﻿import concurrent.futures
import multiprocessing
import trace
import importlib
import importlib.util
import traceback
import inspect
from concurrent.futures import ThreadPoolExecutor
from multiprocessing.managers import BaseManager
import multiprocessing.synchronize
import multiprocessing.managers
from billiard.pool import Pool
import configparser
import sqlite3
import sys
import threading
import typing
import os
import random
import time

from datetime import datetime
from datetime import date

import lxml
import lxml.etree
import requests
from requests import *

## http 头标识长度
__httpStrLen = len("http://")

## https 头标识长度
__httpsStrLen = len("https://")

## 当前时间
__nowDataTime = datetime.now()
## 路径
__targetPathDir: str = ""
## 浏览器虚拟对象
__user_agent_list = ["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)", "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"]
## 当前进程管理对象
__manager: multiprocessing.managers.SyncManager = None
## 进程时间锁
__multiProcessingGetRequestTimeLock: multiprocessing.synchronize.Lock = None
## 进程get请求锁
__multiProcessingGetRequestLock: multiprocessing.synchronize.Lock = None
## 数据库锁
__multiProcessingGetDBLock: multiprocessing.synchronize.Lock = None
## 读写锁
__multiProcessingRWfileLock: multiprocessing.synchronize.Lock = None
## 进程请求间隔
__multiProcessSecs: multiprocessing.managers.ValueProxy[dict[str, multiprocessing.synchronize.Lock]] = None
## 进程锁
__multiProcessLock: multiprocessing.managers.ValueProxy[dict[str, multiprocessing.synchronize.Lock]] = None
## 进程映射
__multiProcessDic: multiprocessing.managers.ValueProxy[dict[str, multiprocessing.synchronize.Lock]] = None
## 建议进程数
__multiProcessCount = 4
## 线程数量
__workerThreads = 5


def getSysExcInfoErrorMsg() -> str:
	"""
	获取因异常诞生的信息
	:return: 异常信息
	"""
	try:
		exc_type, exc_value, exc_traceback = sys.exc_info()
		errorList = traceback.format_tb(exc_traceback)
		rumCmd = "运行配置:\n\t"
		for arg in sys.argv:
			rumCmd = f"{rumCmd} {arg}"
		rumCmd = f"{rumCmd}\n"
		writeContent = ""
		if errorList is not None and len(errorList) != 0:
			for item in errorList:
				writeContent = f"{writeContent}{item}"

		writeContent = f"\n===========\n{rumCmd}错误类型:{exc_type}\n错误信息:{exc_value}\n错误堆栈:{writeContent}\n===========\n"
		return writeContent
	except:
		traceback.print_exc()
		return "error getSysExcInfoErrorMsg"


def __stdError(msg: str):
	global __multiProcessingGetRequestTimeLock
	try:
		if __multiProcessingGetRequestTimeLock is None:
			initLib()
		__multiProcessingGetRequestTimeLock.acquire()
		sys.stderr.write(msg)
	except:
		sys.stderr.write(f"{getSysExcInfoErrorMsg()}")
	finally:
		__multiProcessingGetRequestTimeLock.release()


def initLib() -> bool:
	global __multiProcessingGetRequestTimeLock, __multiProcessLock, __manager, __multiProcessingGetRequestLock, __multiProcessingGetDBLock, __multiProcessingRWfileLock, __multiProcessSecs, __multiProcessDic, __multiProcessLock

	try:
		if __multiProcessingGetRequestTimeLock is None:

			__multiProcessingGetRequestTimeLock = multiprocessing.Lock()
			## 进程get请求锁
			__multiProcessingGetRequestLock = multiprocessing.Lock()
			## 数据库锁
			__multiProcessingGetDBLock = multiprocessing.Lock()
			## 读写锁
			__multiProcessingRWfileLock = multiprocessing.Lock()
			## 词典构造器 # multiprocessing.managers.SyncManager
			__manager = multiprocessing.Manager()
			## 请求进程的锁词典
			__multiProcessLock = __manager.dict()
			## 进程时间词典
			__multiProcessSecs = __manager.dict()
			## 进程映射
			__multiProcessDic = __manager.dict()
		return True
	except:
		__stdError(getSysExcInfoErrorMsg())
		return False


def getLibManager() -> multiprocessing.managers.SyncManager:
	global __manager
	initLib()
	return __manager


def getRWFileLock() -> multiprocessing.synchronize.Lock:
	"""
	返回一个读写锁
	:return:
	:rtype:
	"""
	global __multiProcessingRWfileLock
	return __multiProcessingRWfileLock


def getDownLoadIniFilePath() -> str:
	"""
	获取下载配置文件
	:return: 路径
	"""
	return getCallAbsDirPath() + "/out/ini/downLoad.ini"


def getTypesIniFilePath() -> str:
	"""
	获取下载类型配置文件
	:return: 路径
	"""
	return getCallAbsDirPath() + "/out/ini/types/"


def getFilterIniFilePath() -> str:
	"""
	获取过滤配置文件
	:return: 路径
	"""
	return getCallAbsDirPath() + "/out/ini/filter/filter"


def setWorkerThreadNumber(workThreadNumBer: int):
	"""
	设置线程数量
	@param workThreadNumBer: 线程数量，小于0表示自动
	"""
	global __workerThreads
	if workThreadNumBer > 0:
		__workerThreads = workThreadNumBer
	else:
		__workerThreads = 8


def getWorkerThreadNumber() -> int:
	"""
	获取建议线程数量
	@return:
	@rtype:
	"""
	global __workerThreads
	if __workerThreads > 0:
		return __workerThreads
	return 8


def setWorkerMultiProcessCount(multiProcessCount: int):
	"""
	设置线程数量
	@param multiProcessCount: 线程数量，小于0表示自动
	"""
	global __multiProcessCount
	if __multiProcessCount > 0:
		__multiProcessCount = multiProcessCount
	else:
		__multiProcessCount = 8


def getWorkerMultiProcessCount() -> int:
	"""
	获取建议线程数量
	@return:
	@rtype:
	"""
	global __multiProcessCount
	if __multiProcessCount > 0:
		return __multiProcessCount
	return 8


def getLibInitTime() -> datetime:
	"""
	获取脚本初始化时间
	:return: 时间
	"""
	global __nowDataTime
	return __nowDataTime


def getMultiProcessRequestLock() -> multiprocessing.synchronize.Lock:
	"""
	获取请求锁
	@return:
	@rtype:
	"""
	global __multiProcessingGetRequestLock
	return __multiProcessingGetRequestLock


def getMultiProcessTimeLock() -> multiprocessing.synchronize.Lock:
	"""
	获取时间锁
	@return:
	@rtype:
	"""
	global __multiProcessingGetRequestTimeLock
	initLib()
	return __multiProcessingGetRequestTimeLock


def getMultiProcessDBLock() -> multiprocessing.synchronize.Lock:
	"""
	获取数据库锁
	@return:
	@rtype:
	"""
	global __multiProcessingGetDBLock
	initLib()
	return __multiProcessingGetDBLock


def getMultiprocessLock(url) -> multiprocessing.synchronize.Lock:
	global __multiProcessingGetRequestTimeLock, __multiProcessLock, __manager

	try:
		initLib()
		__multiProcessingGetRequestTimeLock.acquire()
		## 每次请求间隔
		resultObj = __multiProcessLock.get(f"{url}.Lock()")
		if resultObj is None:
			__multiProcessingGetRequestTimeLock.release()
			setMultiprocessLock(url)
			__multiProcessingGetRequestTimeLock.acquire()
			resultObj = __multiProcessLock.get(f"{url}.Lock()")
		return resultObj
	except:
		sys.stderr.write(getSysExcInfoErrorMsg())
	finally:
		__multiProcessingGetRequestTimeLock.release()


def setMultiprocessLock(url, lock=None):
	"""
	设置请求时间
	进程共享级别
	:return: 进程时间
	"""
	global __multiProcessingGetRequestTimeLock, __multiProcessLock, __manager
	try:
		initLib()
		__multiProcessingGetRequestTimeLock.acquire()
		if lock is None:
			lock = __manager.Lock()
		## 每次请求间隔
		__multiProcessLock[f"{url}.Lock()"] = lock
	except:
		sys.stderr.write(getSysExcInfoErrorMsg())
	finally:
		__multiProcessingGetRequestTimeLock.release()


def getMultiprocessSecs(url: str) -> int:
	"""
	获取请求时间
	进程共享级别
	:return: 进程时间
	"""
	global __multiProcessingGetRequestTimeLock, __multiProcessSecs

	try:
		initLib()
		__multiProcessingGetRequestTimeLock.acquire()
		## 每次请求间隔
		resultObj = __multiProcessSecs.get(url)
		if resultObj is None:
			return 0
		return resultObj
	except:
		sys.stderr.write(getSysExcInfoErrorMsg())
	finally:
		__multiProcessingGetRequestTimeLock.release()


def setMultiprocessSecs(url, newSece: int = 0):
	"""
	设置请求时间
	进程共享级别
	:return: 进程时间
	"""
	global __multiProcessingGetRequestTimeLock, __multiProcessSecs
	try:
		initLib()
		__multiProcessingGetRequestTimeLock.acquire()
		## 每次请求间隔
		__multiProcessSecs[url] = newSece
	except:
		sys.stderr.write(getSysExcInfoErrorMsg())
	finally:
		__multiProcessingGetRequestTimeLock.release()


def currentCallThisFunctionFileLine() -> (str, str):
	"""
	返回调用者行号
	:return: 行号
	"""
	return inspect.getframeinfo(inspect.stack()[1][0]).lineno


def currentCallThisFunctionFileName() -> str:
	"""
	返回调用者文件名称
	:return: 文件名称
	"""
	return inspect.getframeinfo(inspect.stack()[1][0]).filename


def createFolder(path: str) -> bool:
	"""
	创建路径目录
	:param path: 路径目录
	:return: 成功返回 true, 否则失败
	"""
	if path is None or path == "":
		return False
	if os.path.exists(path):
		return True
	os.makedirs(path)
	return True


def initTargetFolder(fmt: str = None):
	"""
	创建目标目录
	:param fmt: 格式化
	:return: 成功返回 true, 失败飞来 false
	:rtype: 布尔
	"""
	global __targetPathDir, __nowDataTime

	if fmt is None:
		if __targetPathDir is None:
			__targetPathDir = __nowDataTime.strftime('%Y_%m_%d/%H_%M/%S_%f')
	elif len(fmt) == 0:
		fmt = '%Y_%m_%d/%H_%M/%S_%f'
		__targetPathDir = __nowDataTime.strftime(fmt)
	else:
		__targetPathDir = __nowDataTime.strftime(fmt)
	return True


def getTargetFolder() -> str:
	"""
	获取目标目录
	:return: 目录
	:rtype: str
	"""
	global __targetPathDir, __nowDataTime
	if __targetPathDir == "":
		if not initTargetFolder():
			return ""
	return __targetPathDir


def getUrlTargetFolder(url: str, fmt: str = None) -> str:
	"""
	根据 url 获取目标目录一个格式化的目录
	:param url: url 的目录
	:param fmt: 格式化日期， 若为 None，则使用默认
	:type fmt:
	:return:
	:rtype:
	"""
	global __targetPathDir, __nowDataTime
	if len(__targetPathDir.strip()) == 0:
		if not initTargetFolder(fmt):
			return f"{url}"
	if fmt is not None:
		if not initTargetFolder(fmt):
			return f"{url}"
	return f"{__targetPathDir}/{url}"


def getPathAbsName(filePath: str) -> str:
	"""
	返回一个绝对路径
	@param filePath: 路径
	@return: 绝对路径
	"""
	return os.path.normpath(os.path.abspath(filePath))


def getTargetInDirAbsFilePath(path) -> list[str]:
	"""
	递归查找所有文件，返回的是绝对路径
	@param path: 顶级目录
	@return: 所有文件，如果不存在则返回一个空的数组
	"""
	result: list[str] = []
	absPath = os.path.abspath(path)
	if not os.path.exists(absPath):
		return result
	if os.path.isfile(absPath):
		return [absPath]
	fileAndDirs = os.listdir(absPath)
	isFileResult = 0
	for fileOrDir in fileAndDirs:
		fileOrDir = absPath + os.sep + fileOrDir
		isFileResult = os.path.isfile(fileOrDir)
		if isFileResult:
			result.append(fileOrDir)
		else:
			subList = getTargetInDirAbsFilePath(fileOrDir)
			for subFile in subList:
				result.append(subFile)
			subList.clear()
	return result


def getCallAbsFileName() -> str:
	"""
	获取调用者的文件名。返回的是绝对路径
	@return: 绝对文件名
	"""
	stack = inspect.stack()
	return stack[1].filename


def getBasePythonSpriteName(model: typing.Type, prefix: str = None, suffix: str = None, checkFileSuffix: str = ".py") -> str:
	"""
	获取模块名称
	:param model: 模块对象
	:param prefix: 前缀
	:param suffix: 后缀
	:return: 模块名称
	"""
	return getBaseFileName(model.__file__, prefix=prefix, suffix=suffix, checkFileSuffix=checkFileSuffix)


def getCallAbsDirPath() -> str:
	"""
	获取调用者的目录名。返回的是绝对路径
	@return: 绝对目录
	"""
	stack = inspect.stack()
	return os.path.dirname(stack[1].filename)


def readFile(readFilePath: str, mode: str = "r", encoding="utf-8", errors='ignore') -> typing.Union[str, None]:
	"""
	读取文件, 如果成功返回内容, 失败或内容为空则返回 None
	:param readFilePath: 文件路径
	:param mode: 文件打开类型
	:param encoding: 编码方式
	:param errors: 错误
	:return: 读取内容, 失败或内容为空则返回 空字符串
	"""
	fileContents: str = ""
	if readFilePath is None or not os.path.exists(readFilePath):
		return fileContents
	with open(readFilePath, mode, encoding=encoding, errors=errors) as file:
		while True:
			buffStr = file.read()
			if buffStr is None or len(buffStr) == 0:
				break
			fileContents += buffStr
	return fileContents


def readDirFiles(dirPath: str) -> dict[str, str]:
	"""
	递归获取该路径下的所有文件内容
	@param dirPath: 路径
	@return: 路径与文件内容的映射
	"""
	filePath = ""
	result = {}
	for root, dirs, files in os.walk(dirPath, topdown=False):
		for name in files:
			filePath = os.path.join(root, name)
			result[filePath] = readFile(filePath)
		for name in dirs:
			filePath = os.path.join(root, name)
			subDict = readDirFiles(filePath)
			for fileFullName, fileContent in subDict.items():
				result[fileFullName] = fileContent
	return result


def readsPathsIniFileStrDic(paths: list[str], sort=True, updateFile=False) -> dict[str, list[str]]:
	"""
	返回以为文件路径为关键字，内容为值的配值对，其中值不重复的关键字
	:param paths: 多个路径
	:param sort: 是否排序
	:param updateFile: 是否更新文件
	:return:  文件路径为关键字，内容为值的配值对的词典对象，失败返回 None
	"""
	if paths is None or len(paths) == 0:
		return None
	paths = requestNovelsLib.strListreduce(paths)
	result: dict[int, list[str]] = {}
	readList: list[str] = None
	for filePath in paths:
		filePaths = requestNovelsLib.getPathFilesNames(filePath)
		for fileName in filePaths:
			result[fileName] = readIniFileToKeyList(fileName, sort=sort, updateFile=updateFile)
	return result


def readsPathsIniFileStrToLenStr(paths: list[str], inDirPath: bool = True) -> dict[int, list[str]]:
	"""
	读取路径下的所有文件，并且返回属于文件的关键字，以 '\n' 为分隔符的所有字符串
	:param paths: 多个路径
	:param inDirPath: 递归查找文件夹
	:return: 返回以关键字的长度为关键字的词典
	"""
	if paths is None or len(paths) == 0:
		return None
	paths = strListreduce(paths)
	result: dict[int, list[str]] = {}
	## 字符长度
	strLen = 0
	checkKey = ""
	exisKey = ""
	filePath = ""
	fileName = ""
	readList: list[str] = None
	fileKeyList: list[str] = []
	## 排重
	for filePath in paths:
		filePaths = getPathFilesNames(filePath, inDirPath=inDirPath)
		for fileName in filePaths:
			readList = readFile(fileName).split("\n")
			for exisKey in readList:
				exisKey = strRemoveSpaces(exisKey)
				if exisKey.isspace():
					continue
				strLen = len(exisKey)
				if strLen == 0:
					continue
				exisArray = result.get(strLen)
				if exisArray is None:
					result[strLen] = [exisKey]
					fileKeyList.append(exisKey)
				elif len(exisArray) == 0 or not strListIsKey(checkKey, exisArray):
					exisArray.append(exisKey)
					fileKeyList.append(exisKey)
			fileKeyList.sort()
			fileKeyList.sort(key=len)
			writeFile("\n".join(fileKeyList) + "\n", fileName)
			fileKeyList.clear()

	return result


def getPathFilesNames(path: str, inDirPath: bool = True) -> list[str]:
	"""
	获取路径下的所有文件
	@param path: 路径
	@param inDirPath: 是否递归
	@return: 所有文件
	"""
	result: list[str] = []
	if not os.path.exists(path):
		return result
	if os.path.isfile(path):
		fileAllPath = os.path.abspath(path)
		result.append(fileAllPath)
	else:
		dirName = None
		for root, dirs, files in os.walk(path, topdown=inDirPath):
			for file in files:
				fileAllPath = os.path.abspath(f"{root}{os.sep}{file}")
				fileAllPath = os.path.normpath(fileAllPath)
				result.append(fileAllPath)
	return result


def __checkFileIsRmove(path: str, ings: list[str] = None, ingBaseNames: list[str] = None, ingExes: list[str] = None) -> bool:
	"""
	检查文件名是否适配条件
	@param path: 检查路径
	@param ings: 全路径匹配忽略
	@param ingBaseNames: 基本文件名
	@param ingExes: 扩展文件名
	@return: true 表示存在适配
	"""
	if ingBaseNames:
		currentBaseName = getBaseFileName(path)
		for ingbBaseName in ingBaseNames:
			ingbBaseName = getBaseFileName(ingbBaseName)
			if ingbBaseName == currentBaseName:
				return True
	if ings and strListIsKey(path, ings):
		return True
	if ingExes:
		index = path.rfind(".")
		if index != -1:
			path = path[index + 1:]
			for item in ingExes:
				index = item.rfind(".")
				if index == -1:
					continue
				item = item[index + 1:]
				if path == item:
					return True
	return False


def removePath(path: str, ings: list[str] = None, ingBaseNames: list[str] = None, ingExes: list[str] = None) -> list[str]:
	"""
	删除指定文件或者目录
	@param path: 路径
	@param ings: 忽略路径
	@param ingBaseNames: 忽略文件名
	@param ingExes: 忽略扩展名
	@return: 被删除的内容
	"""

	if not os.path.exists(path):
		return []
	path = os.path.abspath(path)
	if os.path.isfile(path):
		try:
			if not __checkFileIsRmove(path, ings, ingBaseNames, ingExes):
				os.remove(path)
				return [path]
			print(f"跳过 : {path}")
			return []
		except:
			msg = f"{traceback.format_exc()}\n====\n{path}\n====="
			__stdError(msg)
			writeLogFile(msg, httpUrl="remove path error")
	else:
		result: list[str] = []
		try:
			files = getPathFilesNames(path)
			jion: list[str] = []
			for removeItem in files:
				if __checkFileIsRmove(removeItem, ings, ingBaseNames, ingExes):
					jion.append(removeItem)
			isSpaceDir = True
			for root, dirs, files in os.walk(path):
				# 第一步：清空文件夹，并且删除
				for name in dirs:
					removeTarget = os.path.join(root, name)
					for subPath in removePath(removeTarget, ings, ingBaseNames, ingExes):
						result.append(subPath)
				# 第二步：删除文件
				for name in files:
					removeTarget = os.path.join(root, name)
					if strListIsKey(removeTarget, jion):
						print(f"跳过 : {removeTarget}")
						continue
					os.remove(removeTarget)  # 删除文件
					result.append(removeTarget)
				try:
					if len(getPathFilesNames(root)) == 0:
						os.rmdir(root)
						result.append(root)
				except:
					msg = getSysExcInfoErrorMsg()
					__stdError(msg)
		except:
			msg = f"{getSysExcInfoErrorMsg()}\n====\n{path}\n====="
			__stdError(msg)
			writeLogFile(msg, httpUrl="remove path error")
		return result
	return []


def removeFile(fileName: str):
	"""
	删除存在的文件
	:param fileName: 文件
	:type fileName: str
	:return:
	:rtype:
	"""
	if os.path.exists(fileName):
		try:
			os.remove(fileName)
			return True
		except:
			msg = f"{traceback.format_exc()}\n====>\n{fileName}\n"

			__stdError(msg)
			writeLogFile(msg, httpUrl="remove file path error")
	return False


def mkDirs(path: str) -> bool:
	"""
	创建目录
	:param path: 目录
	"""
	try:
		path = os.path.realpath(path)
		path = path.replace("\\", os.path.sep).replace("/", os.path.sep)
		split = path.split(os.path.sep)
		dirPathCount = len(split)
		nowDirName = split[0]
		if not os.path.exists(nowDirName):
			raise IOError(f"({path})not has diect {nowDirName}")
		for index in range(1, dirPathCount):
			nowDirName = nowDirName + os.path.sep + split[index]
			if not os.path.exists(nowDirName):
				try:
					os.mkdir(nowDirName)
				except:
					error_msg = f"{getSysExcInfoErrorMsg()}"
					__stdError(error_msg)
					writeLogFile(error_msg, "mkdir error")
		return True
	except:
		msg = getSysExcInfoErrorMsg()
		__stdError(msg)
		writeLogFile(msg)
		return False


def getResponseAtStr(response: requests.Response, lastStr) -> str:
	"""
	根据请求体返回一个字符串，lastStr 将会跟随 url 末尾
	:param response: 检测的 请求体
	:param lastStr: 追加的字符串
	"""
	urlName = findUrlName(response.url)
	httpNodeName = response.url[0:response.url.find(':')]
	if lastStr is not None and len(lastStr) != 0:
		return f"{httpNodeName}://{urlName}/{lastStr}"
	return f"{httpNodeName}://{urlName}"


def findUrlName(HttpUrl: str) -> str:
	"""
	返回一个不带 http 或 https 与相关参数的网站 url
	:param HttpUrl: 检测的 url
	:return: 一个标准的 url，除非用户输入的 rul 异常
	"""
	## 开始位置
	startFindIndex = HttpUrl.rfind(":")
	if startFindIndex != -1:
		HttpUrl = HttpUrl[startFindIndex + 1:]
	startFindIndex = HttpUrl.find("//")
	if startFindIndex != -1:
		## 去掉开始的 "//"
		HttpUrl = HttpUrl[startFindIndex + 2:]
	startFindIndex = HttpUrl.find("/")
	if startFindIndex == -1:
		return HttpUrl
	## 去掉末尾的 "/*."
	HttpUrl = HttpUrl[0:startFindIndex]
	return HttpUrl


def writeFile(contents, path, mode="w", encoding="utf-8", clear: bool = True) -> bool:
	"""
	写入文件
	:param contents: 内容
	:param path: 文件路径
	:param mode: 写入模式
	:param encoding: 写入编码
	:param clear: 是否清理内容
	:param appendStarStr: 追加在 conten 前的字符串
	:param appendLastStr: 追加在 conten 后的字符串
	"""
	try:
		fileAbspath = os.path.abspath(path)
		if not os.path.exists(path):
			# 检测目录
			dirPath = os.path.split(fileAbspath)[0]
			if not os.path.exists(dirPath):
				mkDirs(dirPath)
		if clear and mode.find("a") == -1:
			with open(path, mode, encoding=encoding) as file:
				file.seek(0)
				file.truncate()
				file.write(contents)
		else:
			with open(path, mode, encoding=encoding) as file:
				file.write(contents)
		return True
	except:
		msg = f"{getSysExcInfoErrorMsg()}\n==>\n{path}"
		__stdError(msg)
		writeLogFile(msg, httpUrl="write file error")
		return False


def createTargetFolderFile(fileName: str) -> str:
	"""
	在目标目录创建一个文件
	:param fileName: 文件名
	:type fileName: str
	:return: 成功返回 路径, 失败返回 ""
	:rtype: str
	"""
	baseName = os.path.basename(fileName)
	writeFileName = f"{getTargetFolder()}/{baseName}"
	writeFile(writeFileName, "")
	if os.path.exists(writeFileName):
		return ""
	return writeFileName


def changeFileMode(filePath: str, userBackPathDir: str = None, remove: bool = True, replaceMap: typing.Dict[str, str] = None) -> str | None:
	"""
	操作文件, 移动或删除
	:param filePath: 文件路径
	:param userBackPathDir: 目标路径, 仅在移动文件时候使用
	:param remove: 是否删除, 使用 False 表示移动
	:param replaceMap: 替换列表，用于替换 'userBackPathDir' 参数
	:return: 备份后的路径，失败返回 none
	"""
	realpath = os.path.realpath(filePath)
	if os.path.exists(realpath):
		if remove:
			os.remove(realpath)
			return None
		else:
			if replaceMap:
				items = replaceMap.items()
				for key, value in items:
					userBackPathDir = userBackPathDir.replace(key, value)
			realpath = os.path.realpath(filePath)
			file: str = os.path.basename(realpath)
			## fileDir = realpath[0:len(realpath) - len(file)]
			newDir = ""
			if replaceMap:
				newDir = f"{realpath[0:len(realpath) - len(file)]}/{userBackPathDir}/"
				newDir = os.path.realpath(newDir)
			mkDirs(newDir)
			backFilePath = f"{newDir}/{file}"
			shutil.move(realpath, backFilePath)
			return backFilePath


def findIndexListNumber(findList: typing.List, findObj) -> int:
	"""
	查找对象位置
	:param findList: 查找列表
	:type findList: 数组类型
	:param findObj: 查找对象
	:type findObj: 任意类型
	:return: 返回位置, 没有找到返回 -1
	:rtype: 整数型
	"""
	if findList is None or len(findList) == 0 or findObj is None:
		return -1
	index = 0
	for listObj in findList:
		if listObj == findObj:
			return index
		index += 1
	return -1


class PathInfo:
	"""
	路径信息类
	"""

	def __init__(self, currentPath):
		self.__currentPath = currentPath
		self.__currentAbsPath = os.path.normpath(os.path.abspath(self.__currentPath))
		self.__files = []
		self.__dirs = []
		if os.path.exists(self.__currentPath) is False or os.path.isfile(self.__currentPath):
			return
		iterator = os.walk(self.__currentPath, topdown=False)

		for root, dirs, files in iterator:
			for foreachFile in files:
				fileAllPath = os.path.abspath(f"{root}{os.sep}{foreachFile}")
				fileAllPath = os.path.normpath(fileAllPath)
				self.__files.append(fileAllPath)
			for foreachDir in dirs:
				fileAllPath = os.path.normpath(os.path.abspath(f"{root}{os.sep}{foreachDir}{os.sep}"))
				if self.__currentAbsPath != fileAllPath:
					self.__dirs.append(fileAllPath)

	def refresh(self):
		if os.path.exists(self.__currentPath) is False or os.path.isfile(self.__currentPath):
			return
		iterator = os.walk(self.__currentPath, topdown=False)

		for root, dirs, files in iterator:
			for foreachFile in files:
				fileAllPath = os.path.abspath(f"{root}{os.sep}{foreachFile}")
				fileAllPath = os.path.normpath(fileAllPath)
				self.__files.append(fileAllPath)
			for foreachDir in dirs:
				fileAllPath = os.path.normpath(os.path.abspath(f"{root}{os.sep}{foreachDir}{os.sep}"))
				if self.__currentAbsPath != fileAllPath:
					self.__dirs.append(fileAllPath)
		...

	def getDirs(self) -> list[str]:
		return self.__dirs[:]

	def getFiles(self) -> list[str]:
		return self.__files[:]

	def getCurrentPath(self) -> list[str]:
		return self.__currentPath[:]

	def isDir(self) -> bool:
		return os.path.isdir(self.__currentPath)

	def isFile(self) -> bool:
		return os.path.isfile(self.__currentPath)

	def getAbsPath(self) -> str:
		return self.__currentAbsPath


class DirPathMan:

	def __init__(self, path):
		self.__currentPath: str = os.path.normpath(os.path.abspath(path))
		self.__modFileList: list[str] = []

	def exists(self) -> bool:
		return os.path.exists(self.__currentPath)

	def pathType(self):
		"""
		返回路径类型
		:return: 不存在返回 -1，文件返回 0，文件夹返回 1
		:rtype:
		"""
		if not self.exists():
			return -1
		if os.path.isfile(self.__currentPath):
			return 0
		return 1

	def make(self) -> bool:
		"""
		创建目录，如果该路径是一个文件，那么就会删除该路径
		:return: 成功返回 True
		"""
		if os.path.exists(self.__currentPath) and os.path.isfile(path):
			try:
				os.remove(path)
				return [path]
			except:
				msg = f"{getSysExcInfoErrorMsg()}\n====\n{fileName}\n====="
				__stdError(msg)
				writeLogFile(msg, httpUrl="remove path error")
				return False
		return mkDirs(self.__currentPath)

	def remove(self) -> list[str]:
		"""
		删除该路径
		:return: 删除列表
		"""
		return removePath(self.__currentPath)

	def appendMod(self, path: str) -> int:
		"""
		追加一个改变的路径
		:param path: 改变的路径
		:return: 添加状态，1 为成功 -1 已经存在
		"""
		path = os.path.normpath(os.path.abspath(path))
		for item in self.__modFileList:
			if path == item:
				path = None
				break
		if path:
			self.__modFileList.append(path)
			return 1
		return -1

	def removeModOther(self, removeSpaceCurrentDir=True) -> list[str]:
		"""
		删除路径下所有未改变的文件与文件夹
		:return: 删除列表
		"""
		try:
			if not os.path.exists(self.__currentPath):
				return []
			if os.path.isfile(self.__currentPath):
				if removeFile(self.__currentPath):
					return [self.__currentPath]
			pathList: list[str] = getPathFilesNames(self.__currentPath)
			removeItem: str = None
			item: str = None
			appendItem: str = None
			removeResult: list[str] = []
			removeResultLen = 0
			for item in pathList:
				appendItem = item
				for removeItem in self.__modFileList:
					if appendItem == removeItem:
						print(f"发现 ({appendItem}) 文件已经被改动")
						appendItem = None
						break
				if appendItem:
					removeResult.append(appendItem)
					removeResultLen += 1
					print(f"发现 ({appendItem}) 文件未更改 ! 随后执行删除操作")
			result: list[str] = []
			if removeResultLen > 0:
				for removeItem in removeResult:
					pathList = removePath(removeItem)
					for item in pathList:
						result.append(removeItem)
			if removeSpaceCurrentDir:
				for root, dirs, files in os.walk(self.__currentPath, topdown=False):
					if len(dirs) == 0 and len(files) == 0:
						removeListBuff = removePath(self.__currentPath)
						print(f"\t!! 发现 {self.__currentPath} 目录为空，正式删除")
						for removeItem in removeListBuff:
							result.append(removeItem)
						break
					break
			return result
		except:
			msg = f"{getSysExcInfoErrorMsg()}\n===>\n{self.__currentPath}\n"
			__stdError(msg)
			writeLogFile(msg, "remove other file man error")

	def getPath(self) -> str:
		return self.__currentPath


class XpathInfo:
	lock = threading.Lock()
	"""
	扩展于 xpath 的方法
	"""

	def __init__(self, element: lxml.etree._Element):
		self.__element: lxml.etree._Element = element
		self.__subXpath: list[lxml.etree._Element] = []
		self.__len = 0

	def xpath(self, path, namespaces=None, extensions=None, smart_strings=True, **_variables) -> list[lxml.etree._Element]:
		"""
		解析一次 xpath 表达式
		:param _path: xpath 表达式
		:return: 解析完成的表达式
		"""
		self.lock.acquire()
		try:
			xpathElements = self.__element.xpath(_path=path, namespaces=namespaces, extensions=extensions, smart_strings=smart_strings, **_variables)
			self.__subXpath.clear()
			for item in xpathElements:
				self.__subXpath.append(item)
			self.__len = len(self.__subXpath)
		except:
			traceback.print_exc()
		self.lock.release()

		return self.__subXpath[:]

	def getCurrentXpathElements(self) -> list[lxml.etree._Element]:
		"""
		获取旧的表达式，希望他会正常的结果
		:return: 旧的接续数组
		"""
		return self.__subXpath

	def resultLen(self) -> int:
		"""
		获取解析个数
		:return: 数组个数
		"""
		return self.__len


class XpathHtml(XpathInfo):
	"""
	使用 hteml 格式化 xpath 对象
	"""

	def __init__(self, html: str):
		super().__init__(lxml.etree.HTML(html))


class NovelInfo:
	"""
	小说信息
	"""

	def __init__(self, novelRootUrl, novelTypeName: str, novelTypeUrl, novelUrl: str, novelName: str, author: str, updateTime: str, updateTimeFmt: str, lastItem: str, info: str, requestUpdateTime: str = None, additionalField: str = ""):
		"""
		小说信息对象初始化
		:param novelRootUrl: 小说网站 url
		:param novelTypeName: 小说类型名称
		:param novelTypeUrl: 小说类型 url
		:param novelUrl: 小说 url
		:param novelName: 小说名称
		:param author: 小说作者
		:param updateTime: 更新时间
		:param lastItem: 更新项
		:param info: 详细
		"""
		self.author = strRemoveSpaces(author)
		self.novelUrl = strRemoveSpaces(novelUrl)
		self.novelName = strRemoveSpaces(novelName)
		self.updateTime = updateTime.strip()
		self.updateTimeFmt = updateTimeFmt.strip()
		self.lastItem = strRemoveSpaces(lastItem)
		if info and len(info) > 0:
			buffList: list[str] = strSplist(info.replace('”', '”\n\t\t'), ["\\n", "\t"])
			buffStr = ""
			for buff in buffList:
				buff = strRemoveSpaces(buff)
				if len(buff) > 0:
					buffStr = f"{buffStr}\n\t\t{buff}"
			self.info = buffStr
		else:
			self.info = ""
		self.novelRootUrl = strRemoveSpaces(novelRootUrl)
		self.novelTypeUrl = strRemoveSpaces(novelTypeUrl)
		self.novelTypeName = strRemoveSpaces(novelTypeName)
		self.additionalField = additionalField.strip()
		if requestUpdateTime is not None:
			self.requestUpdateTime = requestUpdateTime.strip()
		else:
			self.requestUpdateTime = getLibInitTime().strftime("%Y-%m-%d %H:%M:%S")


class NovelInfoEx:

	def __init__(self, novel: NovelInfo):
		self.novel = novel
		self.urls = [novel.novelUrl]

	def rebuilderKeyWord(self):
		i = len(self.novel.additionalField)
		if i != 0:
			ketStart = "关键字:"
			newAdditionalFieldList = strRemoveSpaces(self.novel.additionalField).replace(ketStart, " ").replace(",", " ").split(" ")
			splitStr: list[str] = []
			## 关键字
			novelAttflide: str = ""
			for appendItemStr in newAdditionalFieldList:
				appendItemStr = strRemoveSpaces(appendItemStr)
				if len(appendItemStr) > 0 and not strListHasKey(appendItemStr, splitStr):
					splitStr.append(appendItemStr)
					novelAttflide = f"{novelAttflide}{appendItemStr}, "
			novelAttflide = novelAttflide.removesuffix(", ")
			self.novel.additionalField = f"{ketStart}{novelAttflide}"

	def appendUrl(self, novex: NovelInfo) -> bool:
		"""
		追加一个 url
		@param novex: 被追加的 url 小说引用对象
		@return: 成功返回 true，失败或者已经存在返回 false
		"""
		if self.novel.novelName == novex.novelName and novex.author == self.novel.author:
			if len(self.novel.info) < len(novex.info):
				self.novel.info = novex.info
			i = len(novex.additionalField)
			if i != 0:
				ketStart = "关键字:"
				splitStr: list[str] = []
				newAdditionalFieldList = strRemoveSpaces(self.novel.additionalField).replace(ketStart, " ").replace(",", " ").split(" ")
				## 关键字
				novelAttflide: str = ""
				for appendItemStr in newAdditionalFieldList:
					appendItemStr = strRemoveSpaces(appendItemStr)
					if len(appendItemStr) > 0 and not strListHasKey(appendItemStr, splitStr):
						splitStr.append(appendItemStr)
						novelAttflide = f"{novelAttflide}{appendItemStr}, "
				spaces_1 = strRemoveSpaces(novex.additionalField)
				replace_2 = spaces_1.replace(ketStart, " ")
				replace_3 = replace_2.replace(",", " ")
				newAdditionalFieldList = replace_3.split(" ")
				for appendItemStr in newAdditionalFieldList:
					appendItemStr = strRemoveSpaces(appendItemStr)
					if len(appendItemStr) > 0 and not strListHasKey(appendItemStr, splitStr):
						novelAttflide = f"{novelAttflide}{appendItemStr}, "
						splitStr.append(appendItemStr)
				novelAttflide = novelAttflide.removesuffix(", ")
				self.novel.additionalField = f"{ketStart}{novelAttflide}"
			for url in self.urls:
				if url == novex.novelUrl:
					return True
			self.urls.append(novex.novelUrl)
			return True
		return False


class ResponseNovelPageList:
	"""
	小说列表对象信息
	"""

	def __init__(self, novelTypeName: str, requestUrl: str, rootUrl: str):
		"""
		列表对象初始化
		:param requestUrl: 小说 url
		:param novelTypeName: 小说名称
		"""
		self.__novelTypeName = novelTypeName
		self.__url = requestUrl
		self.__rootUrl = rootUrl

	@property
	def typeNam(self):
		return self.__novelTypeName

	def get(self, novelDataListFun, novelPageListGetNextUrl, requestCount=-1, timeout=20, sleepTime=0.025, nextRequestTimeSece=0.5, port=None, getAppendRequestNovelPageFunctionPtr=None, endCall=None, novelMaxCount=0, **kwargs) -> typing.Union[list[NovelInfo], None]:
		"""
		开始使用 get 请求， urlDataListFun 若为空，直接返回 None，否则返回调回过后的返回值
		:param novelDataListFun: 回调函数， 第一个参数为响应对象， 返回该有的小说信息
		:param novelPageListGetNextUrl: 回调函数， 第一个参数为响应对象， 返回该有是下一页
			返回的小说数量不一致时，将会不继续调用查找下一页
		:param requestCount: 最大请求数目，如果为 负数，那么它就会无限次请求
		:param timeout: 请求超时时间
		:param sleepTime: 线程错误时候睡眠时间
		:param endCall: 类型爬取结束时调用
		:param novelMaxCount: 最大计数，若为 0，则不计数
		:param getAppendRequestNovelPageFunctionPtr: 获取一个页面请求，它是细化了请求的调用，不需要时设置为 None
		:param kwargs: 请求参数
		"""
		if novelDataListFun is None:
			return None
		novelMaxCount = int(novelMaxCount)
		## 这是一个返回的
		resultList: list[NovelInfo] = []
		## 这个是临时保存
		noveList: list[NovelInfo] = []
		requestUrl = self.__url
		exsiUrl = ""
		getCount = 0
		appenLen = 0
		response = None
		novelResponse = None
		currentItem = None
		requestGetUrlDate = None
		isAppendNovel = True
		noveList: list[NovelInfo] = []
		saveNovelList: list[NovelInfo] = []
		appendRequestNovelPageFunctionPtr = None
		while True:
			if not isAppendNovel:
				break
			saveNovelList.clear()
			appenLen = 0
			try:
				requestGetUrlDate = updateTime = datetime.now()
				response = requestGetUrl(requestUrl, requestCount=requestCount, timeout=timeout, sleepTime=sleepTime, nextRequestTimeSece=nextRequestTimeSece, port=port, kwargs=kwargs)
			except:
				writeLogFile("小说遍历页面失误", requestUrl, response)
				__stdError(f"错误 :\n{getSysExcInfoErrorMsg()}")
				break
			if response is None:
				break
			resultCount = len(resultList)
			noveList = novelDataListFun(response, resultCount, self.__novelTypeName, self.__url, self.__rootUrl)
			if noveList is not None:
				getCount = len(noveList)
				## 是否存在重复的小说 url
				try:
					for item in noveList:
						exsiUrl = None
						try:
							for exsiItem in resultList:
								if exsiItem.novelUrl == item.novelUrl:
									exsiUrl = exsiItem.novelUrl
									break
						except:
							writeLogFile("无法进行更加细节的请求!!", currentItem.novelUrl, response)
							__stdError(f"错误 :\n{getSysExcInfoErrorMsg()}")
						if exsiUrl is None:
							currentItem = item
							resultCount = len(resultList)
							## 是否需要比较个数，需要比较则在个数上限之后吧 requestGetUrlDate 置为 None
							if novelMaxCount > 0:
								if resultCount > novelMaxCount:
									requestGetUrlDate = None
									isAppendNovel = False
							if getAppendRequestNovelPageFunctionPtr and currentItem and len(currentItem.novelUrl) != 0:
								try:
									appendRequestNovelPageFunctionPtr = getAppendRequestNovelPageFunctionPtr(currentItem, novelResponse, requestGetUrlDate, resultCount)
									if appendRequestNovelPageFunctionPtr:
										requestGetUrlDate = updateTime = datetime.now()
										novelResponse = requestGetUrl(currentItem.novelUrl, requestCount=requestCount, timeout=timeout, sleepTime=sleepTime, nextRequestTimeSece=nextRequestTimeSece, port=port, kwargs=kwargs)
										currentItem = appendRequestNovelPageFunctionPtr(currentItem, novelResponse, requestGetUrlDate, resultCount)
								except:
									writeLogFile("无法进行更加细节的请求!!", currentItem.novelUrl, novelResponse)
									__stdError(f"错误 :\n{getSysExcInfoErrorMsg()}")
								finally:
									requestGetUrlDate = None
									novelResponse = None
							if currentItem is not None and isAppendNovel:
								appenLen += 1
								saveNovelList.append(currentItem)
								resultList.append(currentItem)
							if novelMaxCount > 0 and appenLen >= novelMaxCount:
								isAppendNovel = False
								break
							if not isAppendNovel:
								break
				except:
					writeLogFile("鉴定重复时发生错误!!", requestUrl, response)
					__stdError(f"错误 :\n{getSysExcInfoErrorMsg()}")
				finally:
					currentItem = None
			if isAppendNovel:
				try:
					appenLen = len(saveNovelList)
					requestUrl = novelPageListGetNextUrl(response, resultList, saveNovelList, appenLen, self.__novelTypeName, self.__url, self.__rootUrl)
				except:
					writeLogFile("下一页获取失误", requestUrl, response)
					traceback.print_exc()
			if requestUrl is None or len(requestUrl) == 0 or not isAppendNovel:
				break
			if noveList is not None:
				noveList.clear()
		if endCall:
			endCall(self.__novelTypeName, self.__url, self.__rootUrl, requestUrl, isAppendNovel)
		print(f"{self.__novelTypeName}\t请求结束\t--------------->\tend)")
		if len(resultList) == 0:
			return None
		return resultList


class ResponseNovel:
	"""
	回调对象
	小说网站获取列表对象列表
	"""

	def __init__(self, rootUrl: str):
		self.__url = self.__rootUrl = rootUrl

	def get(self, urlDataListFun, requestCount=-1, timeout=20, sleepTime=0.025, nextRequestTimeSece=0.5, port=None, urlRoot: str = None, **kwargs) -> typing.Union[list[ResponseNovelPageList], None]:
		"""
		开始使用 get 请求， urlDataListFun 若为空，直接返回 None，否则返回调回过后的返回值
		:param urlDataListFun: 回调函数， 第一个参数为响应对象， 第二个参数为 XpathInfo
		:param requestCount: 最大请求数目，如果为 负数，那么它就会无限次请求
		:param timeout: 请求超时时间
		:param sleepTime: 线程错误时候睡眠时间
		:param kwargs: 请求参数
		"""
		if urlDataListFun is None:
			return None
		requestUrl = requestGetUrl(self.__url, requestCount=requestCount, timeout=timeout, sleepTime=sleepTime, nextRequestTimeSece=nextRequestTimeSece, port=port, kwargs=kwargs)
		if requestUrl is None:
			return None
		resultDict: typing.Union[dict[str, list[str]], None] = urlDataListFun(requestUrl)
		if resultDict is None or len(resultDict) == 0:
			return None
		items = resultDict.items()
		results: list[ResponseNovelPageList] = []
		if urlRoot is None:
			for (key, values) in items:
				for value in values:
					results.append(ResponseNovelPageList(key, value, self.__url))
		else:
			for (key, values) in items:
				for value in values:
					results.append(ResponseNovelPageList(key, value, urlRoot))
		return results


user_agent_list = ["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.81 Safari/537.36 Edg/104.0.1293.54", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36Edge/13.10586", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
	"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0", "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0", "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.9.168 Version/11.52", "Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.229 Version/11.62"]


def getInfoUserAgentHeader() -> str:
	"""
	随机返回一个浏览器标识
	@return: 浏览器标识
	"""
	return random.choice(user_agent_list)


def getBaseFileName(fileName, prefix: str = None, suffix: str = None, checkFileSuffix: str = None) -> str:
	"""
	获取基本文件名
	:param fileName: 文件路径（包含文件名的路径，该方法检测相对路径和绝对路径）
	:param prefix: 成功后返回加入前缀
	:param suffix: 成功后返回加入后缀
	:param checkFileSuffix: 检查的后缀，默认检测 . 的最后位置
	:return: 返回加入前缀和后缀的基本文件名
	"""
	baseName = os.path.basename(fileName)
	fileLen = len(baseName)
	if checkFileSuffix is not None:
		checkFileSuffixLen = len(checkFileSuffix)
		copyCom = fileLen - checkFileSuffixLen
		fileSuffixLen = baseName[copyCom:]
		if fileSuffixLen == checkFileSuffix:
			baseName = baseName[:copyCom]
	else:
		pointIndex = baseName.rfind(".")
		if pointIndex != -1:
			baseName = baseName[:pointIndex]

	if prefix is not None and prefix != "":
		baseName = f"{prefix}{baseName}"

	if suffix is not None and suffix != "":
		baseName = f"{baseName}{suffix}"
	return baseName


def getPathName(filePath: str) -> str:
	"""
	返回带后缀的文件名称，若为目录，则返回目录名称
	@param filePath: 路径
	@return: 字符串
	"""
	dirPath = os.path.abspath(filePath)
	return dirPath.split(os.sep)[-1]


def getTargetAbsSavePath(file: str) -> str:
	"""
	返回文件所在的绝对路径，若它作为文件夹，则返回文件夹的父级绝对路径
	:param file: 检测而的文件路径
	:return: 路径
	"""
	realpath = os.path.realpath(file)
	if os.path.isdir(realpath):
		return realpath
	return os.path.dirname(realpath)


def getLogPath() -> str:
	"""
	获取写入日志的路径
	"""
	return f"{os.path.dirname(inspect.stack()[1].filename)}{os.sep}log"


def removeLogFiles() -> list[str]:
	"""
	删除日志目录的所有文件
	"""
	return removePath(getLogPath())


def writeLogFile(content: str, httpUrl: str = "None", requestResponse: requests.Response = None, typeStr: str = "normal", path: str = None):
	"""
	写入一个错误日志
	:param content: 写入内容
	:param httpUrl: url
	:param requestResponse: 请求头
	:param typeStr: 类型，影响存储文件路径
	:param path: 所在路径，不填写为默认
	"""
	try:
		nowTimeObj = datetime.now()
		nowTimeStr = nowTimeObj.strftime('%Y 年 %m 月 %d 日 %H 时 %M 分 %S 秒 .%f')
		content = f"\n<!-- {nowTimeStr} : -->\n{content}\n"
		nowTimeStr = nowTimeObj.strftime('%Y_%m_%d_%H_%M_%S_%f')
		if path is None:
			## 写入异常文件
			path = f"{getLogPath()}/{findUrlName(httpUrl)}/{typeStr}/{nowTimeStr}_error.html"
		if requestResponse is not None and requestResponse.text is not None:
			content += requestResponse.text
		writeFile(content, path)
	except:
		__stdError(f"\t写入文件异常: {getSysExcInfoErrorMsg()}\n")


def requestGetUrl(httpUrl: str, requestCount=15, timeout=20, requestSleepTime=20, nextRequestTimeSece=0.5, usrSession=True, getHeaderItem: str = None, maxRequestMsc=80, port=None, **kwargs) -> typing.Union[requests.Response, None]:
	"""
	请求一个链接，返回 requests.Response 对象
	:param httpUrl: 请求链接
	:param requestCount: 最大请求数目，如果为 负数，那么它就会无限次请求
	:param timeout: 请求超时时间
	:param sleepTime: 请求错误时候睡眠时间
	:param nextRequestTimeSece: 请求成功时，下次的请求的等待时间，需要每次都请求完毕时候，才能继续下次请求，而此判断的时间由该参数设定
	:param maxRequestMsc: 最大请求时间
	:param kwargs: 请求参数
	:return: requests.Response 对象
	"""
	urllib3.disable_warnings()
	if maxRequestMsc <= 0:
		maxRequestMsc = 80
	# kwargs['timeout'] = timeout
	# if kwargs.get('headers') is None:
	# 	kwargs['headers'] = {}
	# 	kwargs['headers']["User-agent"] = getInfoUserAgentHeader()
	userHeaders = {"User-agent": getInfoUserAgentHeader(), 'Connection': 'close'}
	currentSleepTime = 0
	proGetRequestSleepTime = 0
	getRequestLock = None
	urllib3.disable_warnings()
	## 会话
	getSession = requests
	if usrSession:
		getSession = requests.Session()
		## 不保存旧的连接点
		getSession.keep_alive = False
	## 增加连接数
	getSession.DEFAULT_RETRIES = 5
	isSetCurrentSleepTimeToHostRequest = False
	wrteLogFileFlage = False
	hostName = findUrlName(httpUrl)
	if port is not None:
		hostPostName = f"{hostName}:{port}"
		httpUrl = httpUrl.replace(hostName, hostPostName, 1)
	try:
		while True:
			wrteLogFileFlage = False
			resultRequest = None
			isSetCurrentSleepTimeToHostRequest = True
			writeContent = ""
			try:
				try:
					getRequestLock = getMultiprocessLock(hostName)
					if getRequestLock is not None:
						getRequestLock.acquire()
						## 获取等待
						proGetRequestSleepTime = getMultiprocessSecs(findUrlName(httpUrl))
						## 请求间隔
						if proGetRequestSleepTime > 0:
							time.sleep(proGetRequestSleepTime)
						## 正式请求
						resultRequest = getSession.get(httpUrl, timeout=timeout, headers=userHeaders, verify=False)
						getRequestLock.release()
						getRequestLock = None
					else:
						## 获取等待
						proGetRequestSleepTime = getMultiprocessSecs(findUrlName(httpUrl))
						## 请求间隔
						if proGetRequestSleepTime > 0:
							time.sleep(proGetRequestSleepTime)
						## 正式请求
						resultRequest = getSession.get(httpUrl, timeout=timeout, headers=userHeaders, verify=False)
				except:
					## kwargs['headers']["User-agent"] = getInfoUserAgentHeader()
					## 写入异常文件
					writeContent = f"<!-- {httpUrl} -->\n<!--\n{getSysExcInfoErrorMsg()}\n-->\n<!--\n======> requestUrl : {httpUrl}\n{traceback.format_exc()} -->\n"
					__stdError(writeContent)
					if not wrteLogFileFlage:
						wrteLogFileFlage = True
						writeLogFile(writeContent, httpUrl, resultRequest, typeStr="getError")
				finally:
					if getRequestLock:
						getRequestLock.release()
						getRequestLock = None
					userHeaders = {"User-agent": getInfoUserAgentHeader(), 'Connection': 'close'}
				if resultRequest is not None:
					if hasattr(resultRequest, "status_code"):
						satusCode = resultRequest.status_code
						if satusCode == 200:
							isSetCurrentSleepTimeToHostRequest = False
							## 代码成功返回
							return resultRequest

				if len(writeContent) != 0:
					writeContent = f"<!-- {httpUrl}  -->\n"

					if not wrteLogFileFlage:
						wrteLogFileFlage = True
						writeLogFile(writeContent, httpUrl, resultRequest, typeStr="resultRequest_is_none")
			except:

				## 写入异常文件
				writeContent = f"<!-- {httpUrl} -->\n<!--\n{getSysExcInfoErrorMsg()}\n-->\n"
				__stdError(f"\nt异常 {writeContent}\n\t\t休眠{currentSleepTime}")
				if not wrteLogFileFlage:
					wrteLogFileFlage = True
					writeLogFile(writeContent, httpUrl, resultRequest, typeStr="call_fun")
			finally:
				if requestCount > 0:
					requestCount -= 1
					if requestCount == 0:
						if not wrteLogFileFlage:
							## 写入异常文件
							writeContent = f"<!--\n!! 超出请求次数{requestCount}\n\t=>>>\t{httpUrl} -->\n<!-- {getSysExcInfoErrorMsg()} \n-->"
							__stdError(writeContent)
							wrteLogFileFlage = True
							writeLogFile(writeContent, httpUrl, resultRequest, typeStr="requestCount")
						break
				currentSleepTime += requestSleepTime
				if maxRequestMsc < currentSleepTime:
					if not wrteLogFileFlage:
						## 写入异常文件
						writeContent = f"<!--\n!! 退出请求{currentSleepTime}\n\t=>>>\t{httpUrl} -->\n<!--\n{getSysExcInfoErrorMsg()}\n-->"
						wrteLogFileFlage = True
						writeLogFile(writeContent, httpUrl, resultRequest, typeStr="currentSleepTime")
					break
	except:

		## 写入异常文件
		writeContent = f"<!-- {httpUrl} -->\n<!--\n{getSysExcInfoErrorMsg()}\n\t\t退出 !!!! -->\n"
		__stdError(writeContent)
		writeLogFile(writeContent, httpUrl, None, typeStr="requestGetUrl error")
	finally:
		## 下次请求需要等待时间
		if isSetCurrentSleepTimeToHostRequest:
			setMultiprocessSecs(findUrlName(httpUrl), currentSleepTime)
		else:
			setMultiprocessSecs(findUrlName(httpUrl), nextRequestTimeSece)
	return None


def removeNovelsRepeatAtUrl(novelList: list[NovelInfo]) -> list[NovelInfo]:
	"""
	根据 url 实现去重
	:param novelList: 需要整理的小说列表
	:return: 已经去掉的重复列表
	"""
	result: list[NovelInfo] = []
	currentItem: NovelInfo
	for item in novelList:
		currentItem = item
		for exisItem in result:
			if exisItem.novelUrl == currentItem.novelUrl:
				currentItem = None
				break
		if currentItem is not None:
			result.append(currentItem)
	return result


def dbRowToNovel(resultRow) -> NovelInfo:
	"""
	从行中获取数据
	:param resultRow: 行
	:return: 小说
	"""
	return NovelInfo(novelUrl=resultRow[0], novelName=resultRow[1], author=resultRow[2], updateTime=resultRow[3], lastItem=resultRow[4], info=resultRow[5], novelTypeName=resultRow[6], novelRootUrl=resultRow[7], novelTypeUrl=resultRow[8], requestUpdateTime=resultRow[9], updateTimeFmt=resultRow[10], additionalField=resultRow[11])


def outputSqlite3(sqlFilePath: str) -> list[NovelInfo]:
	"""
	从数据控中获取小说列表
	:param sqlFilePath: 数据库路径
	:return: 列表，它总是不会是空(None)
	"""
	conn = None
	cur = None
	result: list[NovelInfo] = []
	try:
		conn = sqlite3.connect(sqlFilePath)
		cur = conn.cursor()
		## 检查是否存在表
		sqlCmd: str = 'SELECT count(*) FROM sqlite_master WHERE type="table" AND name = "pageList";'
		cur.execute(sqlCmd)
		resultRows = cur.fetchall()
		exisPageLsit = False
		for resultRow in resultRows:
			if resultRow[0] == 1:
				exisPageLsit = True
				break
		if not exisPageLsit:
			return result
		## 查询以前的，避免重复
		sqlCmd = 'SELECT * FROM  pageList'
		cur.execute(sqlCmd)
		resultRows = cur.fetchall()
		novel = None
		for resultRow in resultRows:
			## 复原一些数据
			exisPageLsit = False
			novel = None
			try:
				novel = dbRowToNovel(resultRow)
			except:
				msg = f"===>\n\t{getSysExcInfoErrorMsg()}\n=>>>>\n"
				writeLogFile(msg, "novelOutputSqlite3 call dbRowToNovel error")
				__stdError(msg)
			## 保存到对象列表
			if novel:
				result.append(novel)
		return removeNovelsRepeatAtUrl(result)
	except:
		msg = requestNovelsLib.getSysExcInfoErrorMsg()
		requestNovelsLib.__stdError(msg)
		requestNovelsLib.writeLogFile(msg, "outputSqlite3 call error")
	finally:
		if cur is not None:
			cur.close()
		if conn is not None:
			conn.close()
	return result


def inputSqlite3(sqlFilePath: str, novelList: list[NovelInfo]) -> bool:
	"""
	录入到 SQLITE 中
	:param sqlFilePath: sql 文件路径
	:param novelList: 小说列表
	"""

	if novelList is None or len(novelList) == 0:
		return False
	initLib()
	dbLock = getMultiprocessLock(sqlFilePath)
	if dbLock is None:
		dbLock = getLibManager().Lock()
		setMultiprocessLock(sqlFilePath, dbLock)
	conn = None
	cur = None
	path = os.path.realpath(sqlFilePath)
	path = os.path.dirname(path)
	mkDirs(path)
	changeArray = removeNovelsRepeatAtUrl(novelList)
	try:
		with dbLock:
			conn = sqlite3.connect(sqlFilePath)
			cur = conn.cursor()
			## 检查是否存在表
			sqlCmd: str = 'SELECT count(*) FROM sqlite_master WHERE type="table" AND name = "pageList";'
			cur.execute(sqlCmd)
			resultRows = cur.fetchall()
			exisPageLsit = False
			for resultRow in resultRows:
				if resultRow[0] == 1:
					exisPageLsit = True
					break

			## 存储sql获取
			exisNovelList: typing.List[NovelInfo] = []
			## 存储新的
			newNovelList: typing.List[NovelInfo] = []
			if exisPageLsit:
				## 查询以前的，避免重复
				sqlCmd = 'SELECT * FROM  pageList'
				cur.execute(sqlCmd)
				resultRows = cur.fetchall()
				novel = None
				for resultRow in resultRows:
					## 复原一些数据
					try:
						novel = dbRowToNovel(resultRow)
					except Exception as es:
						print(f"===>\n\t{es}\n=>>>>\n")
					## 保存到对象列表
					if novel is not None:
						exisNovelList.append(novel)
				currentItemLen = len(exisNovelList)
				exisNovelList = removeNovelsRepeatAtUrl(exisNovelList)
				## 不存在重复，则继续匹配
				if len(exisNovelList) == currentItemLen:
					exisPageLsit = True

				## 重复，则需要重建表
				else:
					exisPageLsit = False
					for currentItem in exisNovelList:
						changeArray.append(currentItem)
					sqlCmd = 'drop table if exists pageList'
					cur.execute(sqlCmd)

			if not exisPageLsit:
				sqlCmd = 'CREATE TABLE pageList(url text, novelName text,novelAuthor text, updateTime text, lastItem text, info text, novelTypeName text, novelRootUrl text, novelTypeUrl text, requestUpdateTime text, updateTimeFmt text, attFlide text)'
				cur.execute(sqlCmd)
				newNovelList = changeArray
			else:
				## 存储更新
				updateNovelList: list[NovelInfo] = []
				## 匹配现有与新存在
				for novelItem in changeArray:
					## 是否现存
					exisPageLsit = False
					for currentExis in exisNovelList:
						if currentExis.novelUrl == novelItem.novelUrl:
							exisPageLsit = True
							break
					if exisPageLsit:
						updateNovelList.append(novelItem)
					else:
						newNovelList.append(novelItem)
				## 更新旧的项
				sqlCmd = f'UPDATE pageList SET updateTime = ? , lastItem =?, requestUpdateTime = ? , updateTimeFmt = ?, attFlide = ?, info = ? where url = ?'
				temp = ()
				for novelItem in updateNovelList:
					try:
						temp = (novelItem.updateTime, novelItem.lastItem, novelItem.requestUpdateTime, novelItem.updateTimeFmt, novelItem.additionalField, novelItem.info, novelItem.novelUrl)
						cur.execute(sqlCmd, temp)
					except Exception as es:
						writeLogFile(f"小说更新异常\t{novelItem.novelName} : {novelItem.novelUrl}", httpUrl=novelItem.novelRootUrl)
						__stdError(traceback.format_exc())
			## 回滚
			## 存储新的项
			temp = ()
			sqlCmd = f'INSERT INTO pageList VALUES( ?,? , ?, ?, ?,?, ?, ?, ?, ?, ?, ?)'
			for novelItem in newNovelList:
				try:
					temp = (novelItem.novelUrl, novelItem.novelName, novelItem.author, novelItem.updateTime, novelItem.lastItem, novelItem.info, novelItem.novelTypeName, novelItem.novelRootUrl, novelItem.novelTypeUrl, novelItem.requestUpdateTime, novelItem.updateTimeFmt, novelItem.additionalField)
					cur.execute(sqlCmd, temp)
				except Exception as es:
					writeLogFile(f"小说写入异常\t{novelItem.novelName} : {novelItem.novelUrl}", httpUrl=novelItem.novelRootUrl)
					__stdError(traceback.format_exc())
			## 回滚
			## 提交
			conn.commit()
			return True
	except:
		name = getBaseFileName(sqlFilePath)
		writeLogFile(f"写入遭遇致命错误", httpUrl=name)
		traceback.print_exc()
		## 回滚
		conn.rollback()
		return False
	finally:
		if cur is not None:
			cur.close()
		if conn is not None:
			conn.close()


def novelInfoTypeSplit(novelList: list[NovelInfo]) -> typing.Dict[str, list[NovelInfo]]:
	"""
	按照类型分割小说
	:param novelList: 分割的数组
	:return: 分割完毕的小说
	"""
	result: typing.Dict[str, list[NovelInfo]] = {}
	for item in novelList:
		if result.get(item.novelTypeName) is None:
			result[item.novelTypeName] = []
		result[item.novelTypeName].append(item)
	return result


def novelInfoTypeSort(novelList: list[NovelInfo]) -> list[NovelInfo]:
	"""
	小说列表进行排序
	:param novelList: 排序小说
	:return: 已经排序的小说列表
	"""
	result: list[NovelInfo] = []
	## 保存插入位置
	resultIndex = 0
	## 保存长度
	resultLen = 0
	if novelList is None or len(novelList) == 0:
		return []
	for novel in novelList:
		resultLen = len(result)
		if resultLen == 0:
			result.append(novel)
		else:
			## 判断插入位置
			resultIndex = 0
			for compItem in result:
				if compItem.updateTime < novel.updateTime:
					break
				if compItem.updateTime == novel.updateTime and compItem.requestUpdateTime < novel.requestUpdateTime:
					break
				resultIndex += 1
			result.insert(resultIndex, novel)
	return result


def novelListToStr(novelList: list[NovelInfo]) -> str:
	"""
	小说数组转换到字符串
	@param novelList: 数组
	@return: 已经转换完毕的字符串
	"""
	result = ""
	arrayCount = len(novelList)
	if novelList is None or arrayCount == 0:
		return result
	currentCount = 1
	for item in novelList:
		if item.additionalField and len(item.additionalField) > 0:
			result = f"{result}< ========= >\t{arrayCount} / {currentCount}\n\t小说名称:\t{item.novelName}\n\t小说作者:\t{item.author}\n\t更新时间:\t{item.updateTime}\n\t获取时间:\t{item.requestUpdateTime}\n\t更新项:\t\t{item.lastItem}\n\t小说详情:\t{item.info}\n\t小说url:\t{item.novelUrl}\n\t{item.additionalField}\n< ========= >\n"
		else:
			result = f"{result}< ========= >\t{arrayCount} / {currentCount}\n\t小说名称:\t{item.novelName}\n\t小说作者:\t{item.author}\n\t更新时间:\t{item.updateTime}\n\t获取时间:\t{item.requestUpdateTime}\n\t更新项:\t\t{item.lastItem}\n\t小说详情:\t{item.info}\n\t小说url:\t{item.novelUrl}\n< ========= >\n"
		currentCount += 1
	return result


def checkUpDate(strTime: str = None, strFrom: str = None, nowTime=None, overDay=2) -> bool:
	"""
	检查超出时间
	:param strTime: 被检查时间，若为空或者长度为 0，则永远返回 True
	:param strFrom: 被检查时间的格式，若为空或者长度为 0，则永远返回 True
	:return: True 表示时间正常, False 表示检查的时间已经超出
	"""
	global __nowDataTime
	try:
		if strTime is None or len(strTime) == 0 or strFrom is None or len(strFrom) == 0:
			return True
		if nowTime is None:
			nowTime = __nowDataTime
		novelUpdateTime = datetime.strptime(strTime, strFrom)
		if novelUpdateTime.year == 1900:
			novelUpdateTime = datetime(nowTime.year, novelUpdateTime.month, novelUpdateTime.day, novelUpdateTime.hour, novelUpdateTime.minute, novelUpdateTime.second)
		compResult = nowTime - novelUpdateTime
		seconds = compResult.total_seconds()

		if seconds < 1.0:
			return False
		day = seconds / 60.0 / 60.0 / 24.0
		if day > overDay:
			return False
	except:
		error_msg = getSysExcInfoErrorMsg()
		__stdError(error_msg)
		writeLogFile(error_msg, "time_comp_error")
	return True


def dateTimeComp(leftTime: str, rightTime: str, strFrom: str, resultType=0) -> int:
	"""
	比较两个时间，如果大于 0，表示 leftTime > rightTime，如果等于 0，则相等，否则 leftTime < rightTime
	:param leftTime: 左操作数
	:param rightTime: 右操作数
	:param strFrom: 时间格式
	:param resultType: 返回的类型 0 表示天，1 表示小时， 2 表示分钟，其他表示秒
	:return: 天数
	"""
	if strFrom is None or len(strFrom) == 0:
		raise TypeError("strFrom 不能为空")
	newLeftTime = datetime.strptime(leftTime, strFrom)
	newRightTime = datetime.strptime(rightTime, strFrom)
	compResult = newLeftTime - newRightTime
	timeComResult = compResult.total_seconds()
	if resultType == 0:  ## 返回天
		timeComResult = timeComResult / 60.0 / 60.0 / 24.0
	elif resultType == 1:  ## 返回小时
		timeComResult = timeComResult / 60.0 / 60.0
	elif resultType == 2:  ## 返回分钟
		timeComResult = timeComResult / 60.0
	return timeComResult


def __novelInfoExAtWriteFile(novelInfos: list[NovelInfo], writeFileTargetPath: str, infoPath: str = None) -> list[NovelInfoEx]:
	try:
		novelInfoExList: list[NovelInfoEx] = []
		for item in novelInfos:
			novelInfoExAppendObj(novelInfoExList, item)

		return novelInfoExWriteFile(novelInfoExList, writeFileTargetPath, infoPath)
	except:
		msg = getSysExcInfoErrorMsg()
		__stdError(msg)
		writeLogFile(msg, "to novel ex error")
	return []


def getSqlite3DBFileToFile(dbFilePath: str, targetPath: str, fmt="%Y_%m_%d_%H_%M", ispsDict: dict[int, list[str]] = None, hspsDict: dict[int, list[str]] = None) -> typing.Dict[str, list[NovelInfo]]:
	"""
	从一个数据库中读取，并且写入路径
	@param dbFilePath: 数据库路径
	@param targetPath: 目标路径
	@param fmt: 目标格式
	@param isps : 相等匹配
	@param hsps : 存在匹配
	"""
	try:
		if not os.path.exists(targetPath):
			os.mkdir(targetPath)
		elif not os.path.isdir(targetPath):
			os.remove(targetPath)
			os.mkdir(targetPath)
	except:
		msg = getSysExcInfoErrorMsg()
		__stdError(msg)
		writeLogFile(msg, "getSqlite3DBFileToFile error")
	dictNovels: typing.Dict[str, list[NovelInfo]] = None
	try:
		parenPath = getTargetAbsSavePath(targetPath)
		callAbsDirPath = getCallAbsDirPath()
		print(f"读取数据库 : {dbFilePath}")
		sqlNovels = outputSqlite3(dbFilePath)
		## 过滤
		dbBaseName = getBaseFileName(getPathName(dbFilePath))
		print(f"过滤小说 : {dbBaseName}")
		sqlNovels = dictFilterNovels(hspsDict, ispsDict, sqlNovels)
		oldIngFilePath = f"{getCallAbsDirPath()}/out/ini/ingFindInDBFiles/{dbBaseName}.ini"
		with ThreadPoolExecutor(max_workers=getWorkerThreadNumber()) as threaPool:
			dictNovels = novelInfoTypeSplit(sqlNovels)
			writeFileTargetPath = f"{parenPath}{os.sep}{dbBaseName}{os.sep}{dbBaseName}.txt"
			threadResult: concurrent.futures.Future = threaPool.submit(__novelInfoExAtWriteFile, *(sqlNovels, writeFileTargetPath,oldIngFilePath))
			forItems = dictNovels.items()
			newFolder = None
			noveName = ""
			writeListNovelName: list[str] = []
			dirPathMan: DirPathMan = DirPathMan(f"{targetPath}{dbBaseName}{os.sep}")
			writeFlage = False
			dirPathMan.appendMod(writeFileTargetPath)
			for key, value in forItems:
				if len(value) == 0:
					continue
				getResultNovelList = novelInfoTypeSort(value)
				for novel in getResultNovelList:
					noveName = strRemoveSpaces(novel.novelName)
					if len(noveName) != 0:
						writeListNovelName.append(noveName)
					if newFolder is None:
						rootUrl = findUrlName(novel.novelRootUrl)
						if fmt is None:
							newFolder = rootUrl
						else:
							newFolder = getUrlTargetFolder(rootUrl, "%Y_%m_%d_%H_%M")
				if len(getResultNovelList) == 0:
					continue
				novelInfos = novelListToStr(getResultNovelList)
				writeFileTargetPath = f"{parenPath}{os.sep}{newFolder}{os.sep}{key}.txt"
				writeFlage = writeFile(novelInfos, writeFileTargetPath)
				if writeFlage:
					print(f"写入文件 {writeFileTargetPath}")
					dirPathMan.appendMod(writeFileTargetPath)
			removeList = dirPathMan.removeModOther()
			for removeItem in removeList:
				print(f"移除的无用文件 {removeItem}")
			if len(writeListNovelName) > 0:
				writeFile("\n".join(writeListNovelName) + "\n", oldIngFilePath)
			threaPool.shutdown()
	except:
		errorMsg = f"{getSysExcInfoErrorMsg()}\n错误：{dbFilePath} 导出 txt 文件异常"
		__stdError(errorMsg)
		writeLogFile(errorMsg, "read db file and write txt file")
	return dictNovels


def dictFilterNovels(hspsDict, ispsDict, sqlNovels: list[NovelInfo]) -> list[NovelInfo]:
	result: list[NovelInfo] = []
	comp = 0
	if hspsDict and len(hspsDict) > 0:
		comp = 1
	if ispsDict and len(ispsDict) > 0:
		comp += 2
	for compNovel in sqlNovels:
		try:
			if comp == 3:
				if strDictHasStrSubStr(compNovel.novelName, hspsDict):
					continue
				if strDictHasStr(compNovel.novelName, ispsDict):
					continue
			elif comp == 1:
				if strDictHasStrSubStr(compNovel.novelName, hspsDict):
					continue
			elif comp == 2:
				if strDictHasStr(compNovel.novelName, ispsDict):
					continue
			result.append(compNovel)
		except:
			msg = f"{getSysExcInfoErrorMsg()}"
			__stdError(msg)
			writeLogFile(msg, compNovel.novelRootUrl, typeStr="findError")
	return result


def getSqlite3DBFileToFiles(dbDirPath: str, targetPath: str, fmt="%Y_%m_%d_%H_%M", ispsDict: dict[int, list[str]] = None, hspsDict: dict[int, list[str]] = None):
	"""
	从 sqlite 数据库中获取数据，并且写入对应文件
	:param dbDirPath: sqlite3 文件目录，它会递归搜索所有 .db 文件
	:param targetPath: 存储的目标目录， 会根据路径与  fmt 来设置下一个目录，若 fmt 为空，则直接使用 targetPath
	:param fmt: 存储的目标目录，根据路径与  fmt 来设置下一个目录，若 fmt 为空，则直接使用 targetPath
	:param ispsDict : 相等匹配
	:param hspsDict : 存在匹配
	"""

	files = getTargetInDirAbsFilePath(dbDirPath)
	dbFiles = []
	for file in files:
		if file.endswith('.db') and os.path.exists(file) and os.path.isfile(file):
			dbFiles.append(file)
	with Pool(getWorkerMultiProcessCount()) as pool:
		for dbFile in dbFiles:
			try:
				print(f"检测到数据库\t===>  {dbFile}")
				currentPro = pool.apply_async(getSqlite3DBFileToFile, (dbFile, targetPath, fmt, ispsDict, hspsDict))
			except:
				__stdError(getSysExcInfoErrorMsg())
		pool.close()
		pool.join()


def novelInfoExWriteFile(writeListEx: list[NovelInfoEx], writeFilePath: str, infoPath: str = None, isSort=True) -> list[NovelInfoEx]:
	"""
	写入日志
	@param writeListEx: 写入小说列表
	@param writeFilePath: 写入路径
	@param infoPath: 信息日志路径
	@param isSort: 是否排序
	@return: 写入对象
	"""
	result: list[NovelInfoEx] = []
	if writeListEx and len(writeListEx) > 0 and writeFilePath and len(writeFilePath) > 0:
		for rebuilderItem in writeListEx:
			rebuilderItem.rebuilderKeyWord()
			result.append(rebuilderItem)
		resultLen = len(result)
		if resultLen > 0:
			if isSort :
				result = sorted(result, key=sortNovelInfoExKeyName)
				result = sorted(result, key=sortNovelInfoExKeyRequestUpdateime)
				result = sorted(result, key=sortNovelInfoExKeyUpdateime)
				result.reverse()
			currentTime = f"{datetime.now().strftime('%Y年%m月%d日 %H 时 %M 分 %S 秒 %f 毫秒')}\n"
			contenx = f"{currentTime}\n{novelExListAtToStr(result)}"
			writeFilePath = os.path.normpath(writeFilePath)
			writeFile(contenx, writeFilePath)
			writeInfoPath = f"{getCallAbsDirPath()}/out/ini/runtimeInfo/novelInfoExWriteFile.txt"
			writeFile(f"写入:\n\t{writeFilePath}\n", writeInfoPath, "a", clear=False)
			if infoPath :
				contenx = novelExListAtNameStr(result)
				writeFile(contenx, infoPath)
				writeFile(f"\t{infoPath}\n", writeInfoPath, "a", clear=False)
	else:
		if writeFilePath and os.path.exists(writeFilePath) and os.path.isfile(writeFilePath):
			removeFile(writeFilePath)
			print(f"删除无用文件 : {getPathAbsName(writeFilePath)}")
		if infoPath and os.path.exists(infoPath) and os.path.isfile(infoPath):
			removeFile(infoPath)
			print(f"删除无用文件 : {getPathAbsName(infoPath)}")
	return result


def novelInfoExAppendObj(novelExList: list[NovelInfoEx], novel: NovelInfo) -> int:
	"""
	追加对象，如果重复列表将会返回 0，否则返回 1
	@param novelExList: 追加引用的列表
	@param novel: 追加对象
	@return: 同位追加返回 0，新增成员返回 1
	"""
	for item in novelExList:
		if item.appendUrl(novel):
			return 0
	novelExList.append(NovelInfoEx(novel))
	return 1


def mapToNovelInfoExList(dictMap: typing.Dict[str, list[NovelInfo]], writeList: list[NovelInfoEx]):
	for key, value in dictMap.items():
		for item in value:
			novelInfoExAppendObj(writeList, item)


def novelExListAtNameStr(novels: list[NovelInfoEx]) -> str:
	result: str = ""
	for novel in novels:
		result = result + novel.novel.novelName + "\n"
	return result


def novelExListAtToStr(novels: list[NovelInfoEx]) -> str:
	currentCount = 1
	arrayCount = len(novels)
	result = ""
	urls = ""
	isClear = True
	for item in novels:
		for url in item.urls:
			if isClear:
				urls = f"{url}"
				isClear = False
			else:
				urls = f"{urls}\n\t\t{url}"
		isClear = True
		if item.novel.additionalField and len(item.novel.additionalField) > 0:
			result = f"{result}< ========= >\t{arrayCount} / {currentCount}\n\t小说名称:\t{item.novel.novelName}\n\t小说作者:\t{item.novel.author}\n\t更新时间:\t{item.novel.updateTime}\n\t获取时间:\t{item.novel.requestUpdateTime}\n\t更新项:\t\t{item.novel.lastItem}\n\t小说详情:\t{item.novel.info}\n\t小说url:\n\t\t{urls}\n\t{item.novel.additionalField}\n< ========= >\n"
		else:
			result = f"{result}< ========= >\t{arrayCount} / {currentCount}\n\t小说名称:\t{item.novel.novelName}\n\t小说作者:\t{item.novel.author}\n\t更新时间:\t{item.novel.updateTime}\n\t获取时间:\t{item.novel.requestUpdateTime}\n\t更新项:\t\t{item.novel.lastItem}\n\t小说详情:\t{item.novel.info}\n\t小说url:\n\t\t{urls}\n< ========= >\n"
		currentCount += 1
	return result


def sortNovelInfoExKeyName(novel: NovelInfoEx) -> str:
	return novel.novel.novelName


def sortNovelInfoExKeyUpdateime(novel: NovelInfoEx) -> str:
	return novel.novel.updateTime


def sortNovelInfoExKeyRequestUpdateime(novel: NovelInfoEx) -> str:
	return novel.novel.requestUpdateTime


def extrusionDB(dbDirPath: str, targetPath: str, fmt="%Y_%m_%d_%H_%M", ispsDict: dict[int, list[str]] = None, hspsDict: dict[int, list[str]] = None):
	"""
	从 sqlite 数据库中获取数据，并且写入对应文件
	:param dbDirPath: sqlite3 文件目录，它会递归搜索所有 .db 文件
	:param targetPath: 存储的目标目录， 会根据路径与  fmt 来设置下一个目录，若 fmt 为空，则直接使用 targetPath
	:param fmt: 存储的目标目录，根据路径与  fmt 来设置下一个目录，若 fmt 为空，则直接使用 targetPath
	:param ispsDict : 相等匹配
	:param hspsDict : 存在匹配
	"""

	files = getTargetInDirAbsFilePath(dbDirPath)
	dbFiles = []
	for file in files:
		if file.endswith('.db') and os.path.exists(file) and os.path.isfile(file):
			dbFiles.append(file)
	pool: Pool = None
	with Pool(getWorkerMultiProcessCount()) as pool:
		poolResultObjList: list[multiprocessing.pool.ApplyResult] = []
		for dbFile in dbFiles:
			try:
				print(f"检测到数据库\t===>  {dbFile}")
				currentPro = pool.apply_async(getSqlite3DBFileToFile, (dbFile, targetPath, fmt, ispsDict, hspsDict))
				##  getSqlite3DBFileToFile, (dbFile, targetPath, fmt, ispsDict, hspsDict)
				poolResultObjList.append(currentPro)
			except:
				__stdError(getSysExcInfoErrorMsg())
		pool.close()
		pool.join()
		writeListEx: list[NovelInfoEx] = []
		result: typing.Dict[str, list[NovelInfo]] = None
		for poolResult in poolResultObjList:
			result = poolResult.get()
			if result:
				mapToNovelInfoExList(result, writeListEx)
		infoPath = f"{getCallAbsDirPath()}{os.sep}out{os.sep}ini{os.sep}ingFindInDBFiles{os.sep}all.db.ini"
		writeFilePath = f"{targetPath}{os.sep}all.db.txt"
		if len(writeListEx) > 0:
			novelInfoExWriteFile(writeListEx, writeFilePath, infoPath)
		else:
			removeFile(writeFilePath)
			removeFile(infoPath)


def getOverduNovels(novels, day, month, year) -> list[NovelInfo]:
	"""
	获取过期的小说
	@param novels: 小说列表
	@param day: 超出日期
	@param month: 超出月份
	@param year: 超出年份
	@return: 过期的小说列表
	"""
	overduNovels: list[NovelInfo] = []
	for novel in novels:
		getDateTime = datetime.today().strptime(novel.updateTime, novel.updateTimeFmt).date()
		if abs(__nowDataTime.day - getDateTime.day) > day:
			overduNovels.append(novel)
		elif abs(__nowDataTime.month - getDateTime.month) > month:
			overduNovels.append(novel)
		elif getDateTime.year != 1900 and abs(__nowDataTime.year - getDateTime.year) > year:
			overduNovels.append(novel)
	return overduNovels


def __removeRepeateSqlite3DB(dbPathFile: str, day, month, year):
	dbCnn = None
	dbCur = None
	deleteNovelBuff: list[NovelInfo] = []
	resultNovelInfos: list[NovelInfo] = []
	## 开始处理
	sqlNovels = outputSqlite3(dbPathFile)
	if len(sqlNovels) == 0:
		removeFile(dbPathFile)
	dictNovels = novelInfoTypeSplit(sqlNovels)
	forItems = dictNovels.items()
	newFolder = None
	poolResults = []
	try:
		with ThreadPoolExecutor(max_workers=__workerThreads) as pool:
			for key, value in forItems:
				poolResults.append(pool.submit(lambda cxp: getOverduNovels(*cxp), (value, day, month, year,)))
			pool.shutdown(wait=True)
			for result in poolResults:
				resultNovelInfos = result.result()
				if resultNovelInfos is None:
					continue
				for novel in resultNovelInfos:
					deleteNovelBuff.append(novel)
				resultNovelInfos.clear()
	except:
		traceback.print_exc()
	if len(deleteNovelBuff) > 1:
		try:
			dbCnn = sqlite3.connect(dbPathFile)
			dbCur = dbCnn.cursor()
			for removeNovel in deleteNovelBuff:
				## 检查是否存在表
				sqlCmd: str = f"DELETE FROM pageList WHERE url='{removeNovel.novelUrl}';"
				dbCur.execute(sqlCmd)
			dbCnn.commit()
		except:
			traceback.print_exc()
			print(f"path -> {dbPathFile}\n")
		finally:
			if dbCur is not None:
				dbCur.close()
			if dbCnn is not None:
				dbCnn.close()
	...


def removeRepeateSqlite3DB(dbPath: str, day=3, month=0, year=0):
	"""
	移除过期小说
	@param dbPath: 数据库文件路径
	@param day: 超出的日期
	@param month: 超出的月份
	@param year: 超出的年限
	"""
	files = getTargetInDirAbsFilePath(dbPath)
	dbFiles = []
	deleteNovelBuff: list[NovelInfo] = []
	for file in files:
		if file.endswith('.db'):
			dbFiles.append(file)
	with Pool(getWorkerMultiProcessCount()) as processPool:
		for dbFile in dbFiles:
			processPool.apply_async(__removeRepeateSqlite3DB, (dbFile, day, month, year,))
		processPool.close()
		processPool.join()


def strRemoveSpaces(srcStr: str) -> str:
	"""
	删除字符串当中所有的空格
	:param srcStr: 源字符串
	:return: 被移除所有空格的字符串
	"""
	result: list = []

	for strSub in srcStr:
		if strSub.isspace() or strSub == "\ufeff" or strSub == "":
			continue
		result.append(strSub)
	return "".join(result)


def strIsSpaceOrNoneOrZeroLen(checkStr: str) -> bool:
	"""
	检查字符串是否为以下状态
	由空字符组成
	是否为空对象
	是否长度为 0
	:param checkStr: 检查对象
	:return: 满足状态返回 True，否则返回 False
	"""
	if checkStr is None or checkStr.isspace():
		return True
	checkStr = checkStr.strip()
	if len(checkStr) == 0:
		return True
	return False


def strSplist(orgStr: str, spls: list[str]) -> list[str]:
	"""
	切割多个字符串
	:param orgStr: 源字符串
	:param spls: 切割字符串数组
	:return: 被切割后的数组
	"""
	if len(orgStr) == 0:
		return [orgStr]
	splsLen = len(spls)
	if splsLen == 0:
		return [orgStr]
	if splsLen == 0:
		return orgStr.split(spls[0])
	result: list[str] = []
	allSubStr: list[str] = [orgStr]
	for index in range(0, splsLen):
		for item in allSubStr:
			targetList = item.split(spls[index])
			for targetItem in targetList:
				result.append(targetItem)
		allSubStr = result
		result = []
	return allSubStr


def strListRmoveSpace(listObj: list[str], isOrg=False, changeType: int = 0) -> list[str]:
	"""
	移除在列表当中存在的空对象
	:param listObj: 处理列表
	:param isOrg: 是否使用去除空格对象功能，默认为使用
	:param changeType: 消除类型，0表示左右消除，小于0表示消除左侧，大于0表示消除右侧
	:return: 处理结果
	"""
	result: list[str] = []
	if listObj is None or len(listObj) == 0:
		return result
	if isOrg:
		for item in listObj:
			if strIsSpaceOrNoneOrZeroLen(item):
				continue
			result.append(item)
	else:
		if changeType == 0:
			for item in listObj:
				item = item.strip()
				if strIsSpaceOrNoneOrZeroLen(item):
					continue
				result.append(item)
		elif changeType < 0:
			for item in listObj:
				item = item.lstrip()
				if strIsSpaceOrNoneOrZeroLen(item):
					continue
				result.append(item)
		else:
			for item in listObj:
				item = item.rsplit()
				if strIsSpaceOrNoneOrZeroLen(item):
					continue
				result.append(item)
	return result


def strListAtStrip(listObj: list[str], changeType: int) -> list[str]:
	"""
	消除字符串空格
	:param listObj: 消除列表
	:param changeType: 消除类型，0表示左右消除，小于0表示消除左侧，大于0表示消除右侧
	:return: 处理结果
	"""
	result: list[str] = []
	item: str = None
	if changeType == 0:
		for item in listObj:
			item = item.strip()
			if strIsSpaceOrNoneOrZeroLen(item):
				continue
			result.append(item)
	elif changeType < 0:
		for item in listObj:
			item = item.lstrip()
			if strIsSpaceOrNoneOrZeroLen(item):
				continue
			result.append(item)
	else:
		for item in listObj:
			item = item.rsplit()
			if strIsSpaceOrNoneOrZeroLen(item):
				continue
			result.append(item)
	return result


def strListToChange(listObj: list[str], changeType: bool) -> list[str]:
	"""
	字符串全员操作
	:param listObj: 转换内标
	:param changeType: 转换类型，true 表示大写，false 表示小写
	:return: 转换完毕的字符串
	"""
	result: list[str] = []
	item: str = None
	if changeType:
		for item in listObj:
			result.append(item.upper())
	else:
		for item in listObj:
			result.append(item.lower())
	return result


def strRemoveSubStr(checkList: list[str]) -> list[str]:
	"""
	删除数组当中存在子字符串的元素内容
	:param checkList: 检测的对象
	:return: 返回不存在子字符串的数组
	"""
	resultList: list[str] = []
	if checkList is None or len(checkList) == 0:
		return resultList
	item: str = None
	resuItem: str = None
	for item in checkList:
		for resuItem in resultList:
			if item.find(resuItem) != -1:
				item = None
				break
		if item:
			resultList.append(item)

	return resultList


def strListBeReduceAppList(orgListObj: list[str], appendList: list[str]) -> list[str]:
	"""
	在匹配的首个匹配开始追加列表，并且排除以后的相同元素
	:param orgListObj: 源列表
	:param appendList: 追加列表
	:return: 一个列表
	"""
	result: list[str] = []
	orgListObjIsNone = orgListObj is None
	appendListObjIsNone = appendList is None

	## 测试源对象列表
	if orgListObjIsNone:  ## 如果源对象列表为空
		if appendListObjIsNone:  ## 不需要追加列表，则返回一个原始数组
			return result
		else:  ## 存在追加列表，则返回一个去重列表
			return strListreduce(appendList)

	appenLen = len(appendList)
	if appenLen == 0:  ## 追加列表为 0，则直接返回一个去重源列表
		return strListreduce(orgListObj)
	orgLen = len(orgListObj)
	if orgLen == 0:  ## 如果源列表为 0，则直接返回一个去重的追加列表
		return strListreduce(appendList)
	## 匹配第一个追加列表对象的位置
	first = appendList[0]
	testObj = None  ## 临时匹配对象
	orgListObj = strListreduce(orgListObj)
	appendList = strListreduce(appendList)
	for item in orgListObj:
		if item == first:  ## 匹配到位置
			for appendItem in appendList:
				result.append(appendItem)
			first = None  ## 置空对象
			break
		else:
			for appendItem in appendList:
				if item == appendItem:
					item = None
					break
		if item:
			## 比较当前的源列表中的元素是否存在返回列表当中
			for testObj in result:
				if testObj == item:  ## 存在则置空
					item = None
					break
			if item:  ## 对象不空，则添加对象
				result.append(item)
	## 最后都找不到，则开始追加到末尾
	if first:
		for appendItem in appendList:
			result.append(appendItem)
	return result


def strListreduce(recduceListObj: list[str]) -> list[str]:
	"""
	排除重复字符串
	:param recduceListObj: 排除列表
	:param codeToChangeType: 操作类型，
	:type codeToChangeType:
	:param strip:
	:type strip:
	:return:
	:rtype:
	"""
	result: list[str] = []
	if recduceListObj is None or len(recduceListObj) == 0:
		return result
	item: str = None
	resuItem: str = None
	for item in recduceListObj:
		for resuItem in result:
			if item == resuItem:
				item = None
				break
		if item:
			result.append(item)
	return result


def reduceStrList(reduceStrList: list[str], toUp=0, strip=0) -> list[str]:
	"""
	缩小字符串，当长对象中的任意字符或字符串出已有短字符串的时候，删除长字符串，保留段字符串
	@param reduceStrList: 需要缩小的字符串组
	@param toUp: 是否直接转换大写，> 0 表示大写， < 0 表示小写。 0 不转换
	@param strip: 是否直接去除空格，-1 表示去除前置空格， 1 表示去除末尾空格。不等于0，且不属于之前所有情况，则去除首末空格， 0 不工作
	@return: 返回一个缩小的字符串
	"""
	foreachList: list[str] = []
	if len(reduceStrList) == 0:
		return foreachList
	exis = False
	## 唯一化与大写
	for current in reduceStrList:
		## 大小写
		if toUp > 0:
			current = current.upper()
		elif toUp < 0:
			current = current.lower()
		## 去空格
		if strip != 0:
			if strip == -1:
				current = current.lstrip()
			elif strip == 1:
				current = current.rstrip()
			else:
				current = current.strip()

		if len(current) == 0:
			continue
		for resultItem in foreachList:
			if resultItem == current:
				exis = True
				break
		if not exis:
			foreachList.append(current)
		else:
			exis = False

	## 记录长度映射
	saveLenToStrMap: dict[int, list[str]] = {}
	for current in foreachList:
		currentLen = len(current)
		lenKeyStrList = saveLenToStrMap.get(currentLen)
		if lenKeyStrList is None:
			saveLenToStrMap[currentLen] = [current]
		else:
			saveLenToStrMap[currentLen].append(current)

	## 长度排序
	keys = saveLenToStrMap.keys()
	sorKeys: list[int] = []
	index = 0
	for key in keys:
		for sorKey in sorKeys:
			if sorKey > key:
				break
			index += 1
		sorKeys.insert(index, key)

	if len(sorKeys) == 0:
		return []
	## 比较与存储
	result: list[str] = saveLenToStrMap.pop(sorKeys[0])
	buffSaveStrList: list[str] = []
	exis = len(sorKeys)
	forIndex = 0
	saveStr = ""
	for index in range(1, exis):
		currentStrArray: list[str] = saveLenToStrMap.pop(sorKeys[index])
		for currentStr in currentStrArray:
			for resultItem in result:
				if currentStr.find(resultItem) == -1:
					continue
				forIndex = 1
				break
			if forIndex == 0:
				result.append(currentStr)
			else:
				forIndex = 0
	return result


def removeStrIndexElement(adjustment: str, removeArray: list[int]) -> str:
	"""
	获取已经被抛弃指定位置的元素
	@param adjustment: 被检测的字符串
	@param removeArray: 移除的位置指标
	@return: 已经被放弃位置的字符串
	"""
	if adjustment is None or len(removeArray) == 0:
		return adjustment
	arrayCode = list(adjustment)
	result: list[str] = []
	targetName = ""
	adjustmentLen = len(adjustment)
	exisIndex = False
	copyIndexs = removeArray[:]
	## 获取正常字符串
	for nameCharIndex in range(0, adjustmentLen):
		for removeIndex in copyIndexs:
			if nameCharIndex == removeIndex:
				exisIndex = True
				break
		if exisIndex:
			exisIndex = False
			## 删除以及来处理位置
			copyIndexs.remove(nameCharIndex)
		else:
			targetName += adjustment[nameCharIndex]
	return targetName


def removeRepeateUnity(changeAarray: list, numberSort=-1, objCom=-1, toChange=0) -> list:
	"""
	获取非重复的数组
	效果作为移除重复元素
	@param changeAarray: 要检测的数组
	@param numberSort: 长度排序， 0 为取消长度排序
	@param objCom: 对象排序， 0 为取消对象排序
	@return: 从检测数组当中获取到的非重复元素
	"""
	result = []

	runBuff = 0
	if changeAarray is None or len(changeAarray) == 0:
		return changeAarray
	for item in changeAarray:
		runBuff = False
		for comSubItem in result:
			if comSubItem == item:
				runBuff = True
				break
		if not runBuff:
			result.append(item)
	if objCom != 0:
		comBuffArray = result
		result = []
		for item in comBuffArray:
			runBuff = 0
			for exisItem in result:
				if objCom == -1 and item < exisItem:
					break
				if objCom == 1 and item > exisItem:
					break
				runBuff += 1
			result.insert(runBuff, item)
	if numberSort != 0:
		comBuffArray = result
		result = []
		itemLen = 0
		exisItemLen = 0
		for item in comBuffArray:
			runBuff = 0
			for exisItem in result:
				itemLen = len(item)
				exisItemLen = len(exisItem)
				if objCom == -1 and itemLen < exisItemLen:
					break
				if objCom == 1 and itemLen > exisItemLen:
					break
				runBuff += 1
			result.insert(runBuff, item)

	return result


__typeNames = []
__refileTypes: list[str] = None


def getNovelTypeName(userIniFilePath: str = None) -> list[str]:
	"""
	获取所有类型名称的关键字
	@return: 类型名称关键字
	"""
	global __typeNames, __refileTypes
	if __refileTypes is None or len(__refileTypes) == 0:
		if userIniFilePath is None:
			userIniFilePath = getTypesIniFilePath()
		readfilePaths = getPathFilesNames(userIniFilePath)
		writeTypeNamesArray: list[str] = []
		for path in readfilePaths:
			__refileTypes = readFile(path).strip().replace("\r", "").replace(" ", "\n").replace("\t", "\n").split("\n")
			for item in __refileTypes:
				appendItem = strRemoveSpaces(item)
				if len(appendItem) > 0:
					__typeNames.append(appendItem)
					writeTypeNamesArray.append(appendItem)
			writeFile("\n".join(writeTypeNamesArray) + "\n", path)
			writeTypeNamesArray.clear()
		__typeNames = removeRepeateUnity(__typeNames)
		__refileTypes = __typeNames
		return __typeNames
	__typeNames = removeRepeateUnity(__typeNames)
	__refileTypes = __typeNames
	return __typeNames


def threadRequestGetUrl(getPageListNovel, getPageNextUrl, responseNovelPage, rootUrl, newFolder, sqlDbPath, runMain, nextRequestTimeSece, port, getAppendRequestNovelPageFunctionPtr, endCall, novelMaxCount):
	"""
	多线程工作处理内容
	@param getPageListNovel: 获取页面的链接函数
	@param getPageNextUrl: 获取下一页的函数
	@param rootUrl: 首页
	@param responseNovelPage: 请求页对象
	@param sqlDbPath: db 文件的路径
	@param runMain: 是否主要运行
	@param newFolder: 目标目录
	@param endCall: 类型爬取结束时调用
	@param getAppendRequestNovelPageFunctionPtr: 小说详细页面的请求
	"""
	getResultNovelList: list[NovelInfo] = None
	try:
		getResultNovelList = responseNovelPage.get(novelDataListFun=getPageListNovel, novelPageListGetNextUrl=getPageNextUrl, nextRequestTimeSece=nextRequestTimeSece, port=port, getAppendRequestNovelPageFunctionPtr=getAppendRequestNovelPageFunctionPtr, endCall=endCall, novelMaxCount=novelMaxCount)
		try:
			inputSqlite3(sqlDbPath, getResultNovelList)
		except:
			exc_type, exc_value, exc_traceback = sys.exc_info()
			errorList = traceback.format_tb(exc_traceback)
			writeContent = ""
			if errorList is not None and len(errorList) != 0:
				writeContent = errorList[0]
			writeContent = f"<!-- 写入文档异常\n{writeContent} -->\n\n<!-- {traceback.format_exc()} -->"
			writeLogFile(writeContent, rootUrl)
			__stdError(writeContent)
		if runMain:
			getResultNovelList = novelInfoTypeSort(getResultNovelList)
			novelInfos = novelListToStr(getResultNovelList)
			writeFile(novelInfos, f"{getCallAbsDirPath()}/out/getAllMain/{newFolder}/{responseNovelPage.typeNam}.txt")
	except:
		traceback.print_exc()
	return getResultNovelList


def getFlage(url):
	"""
	获取标识
	@param url: url
	@return: 返回一个标识
	"""
	global __multiProcessDic, __multiProcessingGetDBLock
	try:
		__multiProcessingGetRequestLock.acquire()
		result = __multiProcessDic.get(f"{url}.isRun()")
		if result is None:
			return False
		return result
	finally:
		__multiProcessingGetRequestLock.release()


def setFlage(url, flage: bool):
	"""
	设置标识，一般情况设置 True 为运行状态
	@param url: url
	@param flage: 对应的标识
	"""
	global __multiProcessDic, __multiProcessingGetDBLock
	try:
		__multiProcessingGetRequestLock.acquire()
		__multiProcessDic[f"{url}.isRun()"] = flage
	finally:
		__multiProcessingGetRequestLock.release()


__endTime = datetime.now()


def getAyscRequestGetUrlEndTime():
	global __endTime
	return __endTime


def paintRequestWork(rootUrl, getIsRunStatsFlageCallBack, sleepTime, runTimeUserCallPtr=None):
	"""
	工作输出函数
	@param rootUrl: 请求 url
	@param getIsRunStatsFlageCallBack: 持续获取标识位
	@param sleepTime: 该调用执行间隔
	@param runTimeUserCallPtr: 每次执行都会执行该函数，不执行请设置为 None，或者默认
	"""
	global __endTime
	flage = True
	startTime = datetime.now()
	requestHttpsUrl = f"https://{findUrlName(rootUrl)}"
	while True:
		__endTime = datetime.now().__sub__(startTime)
		flage = getIsRunStatsFlageCallBack(rootUrl)
		if not flage:
			print(f"\n\t\t\t运行 {requestHttpsUrl} 。。。 结束: {__endTime}\n")
			break
		else:
			print(f"\n\t\t\t运行 {requestHttpsUrl} 。。。 中:\t{__endTime}\n")
			if runTimeUserCallPtr is not None:
				runTimeUserCallPtr()
		time.sleep(sleepTime)


def getAutoTextCodeToText(source: str, encodeType=None) -> str:
	"""
	从html文本中转码
	:param source : 源文本
	:param encodeType : 可能的编码
	"""
	xpath = XpathHtml(source)
	liNodes = xpath.xpath(r'//meta/@charset')
	if xpath.resultLen() != 0:
		encode = liNodes[0]
		if encodeType is None:
			text = source.encode().decode(encode)
			return text
		text = source.encode(encodeType).decode(encode)
		return text
	liNodes = xpath.xpath(r'//meta/@content')
	if xpath.resultLen() != 0:
		for emunStr in liNodes:
			charsetIndex = emunStr.find("charset")
			if charsetIndex != -1:
				encode: str = emunStr[charsetIndex:]
				charsetIndex = encode.find("=")
				encode: str = encode[charsetIndex + 1:].strip()
				if encodeType is None:
					text = source.encode().decode(encode)
					return text
				text = source.encode(encodeType).decode(encode)
				return text
	return source


def getAutoCodeText(response: requests.Response) -> str:
	"""
	排除编码问题
	自动获取页面的正确内容
	:param response: 请求页面内容
	:return:  页面内容
	"""
	xpath = XpathHtml(response.text)
	liNodes = xpath.xpath(r'//meta/@charset')
	if xpath.resultLen() != 0:
		encode = liNodes[0]
		text = response.content.decode(encode)
		return text
	liNodes = xpath.xpath(r'//meta/@content')
	if xpath.resultLen() != 0:
		for emunStr in liNodes:
			charsetIndex = emunStr.find("charset")
			if charsetIndex != -1:
				encode: str = emunStr[charsetIndex:]
				charsetIndex = encode.find("=")
				encode: str = encode[charsetIndex + 1:].strip()
				text = response.content.decode(encode)
				return text
	return response.text


def ayscRequestGetUrl(requestUrl, getNovelHome, getPageListNovel, getPageNextUrl, rootUrl, newFolder, runMain, proLock: multiprocessing.synchronize.Lock, workerThreadNumber=0, nextRequestTimeSece=0.5, titleSleepTime=10, port=None, getAppendRequestNovelPageFunctionPtr=None, endCall=None, novelMaxCount=0, runTimeUserCallPtr=None):
	"""
	多线程工作处理内容
	@param requestUrl: 首页请求对象
	@param getNovelHome: 获取页面类型的链接函数
	@param getPageListNovel: 获取页面的链接函数
	@param getPageNextUrl: 获取下一页的函数
	@param rootUrl: 首页
	@param newFolder: 目标目录
	@param runMain: 是否主要运行
	@param proLock: 进程锁
	@param port: 请求端口。默认为自动
	@param getAppendRequestNovelPageFunctionPtr: 小说追加的请求
	@param endCall: 类型结束调用
	@param novelMaxCount: 最大数量
	"""
	global __multiProcessSecs, __multiProcessLock, __multiProcessDic, __multiProcessingGetRequestLock, __manager
	if __multiProcessingGetRequestLock is None:
		initLib()

	rootUrlBaseName = findUrlName(rootUrl)
	setMultiprocessLock(findUrlName(rootUrlBaseName), proLock)
	stack = inspect.stack()
	currentJavascriptPath = os.path.dirname(stack[1].filename)
	responseNovelPageList = requestUrl.get(getNovelHome, urlRoot=rootUrl)
	if responseNovelPageList is None or len(responseNovelPageList) == 0:
		return None
	if workerThreadNumber == 0:
		workerThreadNumber = getWorkerThreadNumber()
	with ThreadPoolExecutor(max_workers=workerThreadNumber) as pool:
		setFlage(rootUrl, True)
		dbPath = f"{currentJavascriptPath}{os.sep}/out/db/{rootUrlBaseName}.db"
		threadList = []
		for responseNovelPage in responseNovelPageList:
			try:
				threadList.append(pool.submit(lambda p: threadRequestGetUrl(*p), [getPageListNovel, getPageNextUrl, responseNovelPage, rootUrl, newFolder, dbPath, runMain, nextRequestTimeSece, port, getAppendRequestNovelPageFunctionPtr, endCall, novelMaxCount]))
			except:
				traceback.print_exc()
		lastMake = threading.Thread(target=paintRequestWork, args=(rootUrl, getFlage, titleSleepTime, runTimeUserCallPtr,))
		lastMake.start()
		pool.shutdown(wait=True)
		setFlage(rootUrl, False)
		lastMake.join()
	return getAyscRequestGetUrlEndTime()


def setArrayElement(topArray: list[list], saveArray: list):
	"""
	若 topArrayy 中不存在相同的 array，则保存一个数组到 topArrayy 中
	@param topArray: 存储对象
	@param saveArray: 被保存的对象
	"""
	isExis = False
	saveArrayLen = len(saveArray)
	if saveArrayLen == 0:
		return
	index = 0
	comArrayLen = 0
	for comArray in topArray:
		comArrayLen = len(comArray)
		if saveArrayLen == comArrayLen:
			while index < saveArrayLen:
				if comArray[index] != saveArray[index]:
					break
				index += 1
			if index != comArrayLen:
				continue
			index = saveArrayLen - 1
			if comArray[index] == saveArray[index]:
				return
	topArray.append(saveArray)


def getArrayAssembly(assemblyArray: list, index=0) -> list[list]:
	"""
	获取数组成员允许的任意组合，非乱序组合
	@param assemblyArray: 参考成员
	@return: 组合列表
	"""
	result: list[list] = []

	assemblyArrayLen = len(assemblyArray)
	## 没有元素，直接返回空
	if assemblyArrayLen == 0:
		return result
	## 单独一个元素，返回本身
	result.append(assemblyArray)
	if assemblyArrayLen == 1:
		return result
	## 取出最大下标
	if index >= assemblyArrayLen:
		index = assemblyArrayLen - 1
	## 开始遍历
	currentIndex = 0
	jumpIndex = 0
	for jumpCount in range(1, assemblyArrayLen):
		## 间隔获取
		for jumpSeep in range(1, assemblyArrayLen):
			jumpIndex = jumpSeep
			for startIndex in range(0, assemblyArrayLen):
				template = []
				currentIndex = startIndex
				while currentIndex < assemblyArrayLen:
					if jumpIndex == 0:
						jumpIndex = jumpSeep
						currentIndex += jumpCount
						continue
					template.append(assemblyArray[currentIndex])
					jumpIndex -= 1
					currentIndex += 1
				setArrayElement(result, template)
	return result


def findStrIndex(findStr: str, targetStr: str) -> list[int]:
	"""
	查找字符串中所有的位置
	@param findStr: 被查找的对象
	@param targetStr: 查找的目标
	@return: 所有位置
	"""
	## 返回对象
	result = []
	## 查找的对象长度
	findStrLen = len(findStr)
	if findStrLen == 0:
		return result
	## 匹配字符串的长度
	targetStrLen = len(targetStr)
	if targetStrLen == 0:
		return result
	## 最长可查找长度
	maxLen = findStrLen - targetStrLen
	## 查找下标
	currentIndex = 0
	## 存在标识
	exis = False
	## 开始遍历
	while currentIndex < maxLen:
		findResunt = findStr.find(targetStr, currentIndex)
		if findResunt == -1:
			break
		result.append(findResunt)
		currentIndex += targetStrLen + findResunt

	return result


def removeIndexs(targetStr: str, indexArray: list[int]) -> str:
	"""
	获取删除指定位置的字符串
	@param targetStr: 被检测的字符串
	@param indexArrays: 删除位置列表
	@return: 实现删除位置的字符串
	"""
	result = []
	targetStrLen = len(targetStr)
	if targetStrLen == 0:
		return targetStr
	indexArrayLen = len(indexArray)
	if indexArrayLen == 0:
		return targetStr
	jumpSubCharIndex = 0
	jumpIndex = 0
	strCurrentIndex = 0
	strArray = list(targetStr)
	if jumpIndex < indexArrayLen:
		jumpSubCharIndex = indexArray[jumpIndex]
		jumpIndex += 1
	while strCurrentIndex < targetStrLen:
		if jumpSubCharIndex == strCurrentIndex:
			if jumpIndex < indexArrayLen:
				jumpSubCharIndex = indexArray[jumpIndex]
				jumpIndex += 1
		else:
			result.append(strArray[strCurrentIndex])
		strCurrentIndex += 1
	return "".join(result)


def removeSplitStrMin(targetStr: str, indexArrays: list[list[int]]) -> list[str]:
	"""
	根据 indexArray 删除 targetStr 匹配的下标
	@param targetStr: 被删除对象
	@param indexArray: 匹配的下标组
	@return: 被删除指定下标的所有对象
	"""
	result: list[str] = []
	if len(targetStr) == 0:
		return result
	result.append(targetStr)
	if len(indexArrays) == 0:
		return result
	for spliteArray in indexArrays:
		result.append(removeIndexs(targetStr, spliteArray))
	return result


__NameSeep = [".", "-", ",", "·", "_", " ", "，", "。", ":", "：", ";", "；", "•", "、", "、", ","]


def getNameMaybeStr() -> list[str]:
	"""
	返回可能的名称间隔列表
	:return: 名称间隔列表
	:rtype: list[str]
	"""
	global __NameSeep
	__NameSeep = removeRepeateUnity(__NameSeep)
	return __NameSeep


def getHumanNameList(name: str, sep="·") -> list[str]:
	"""
	根据切分字符串来返回所有可能枚举的名称
	@param name: 被检测的名称
	@param sep: 检测的字符串
	@return: 枚举列表
	"""
	return name.split(sep)  ## return removeSplitStrMin(name, getArrayAssembly(findStrIndex(name, sep)))


def initParamArgs(argvDict: typing.Dict[typing.AnyStr, typing.List[typing.AnyStr]] = None) -> typing.Dict[typing.AnyStr, typing.List[typing.AnyStr]]:
	"""
	使用命令行参数添加到映射列表中
	:param argvDict: 添加对象
	:return: 成功返回对象，失败返回 none
	"""
	argLen = len(sys.argv)
	index = 1
	if argvDict is None:
		argvDict = {}
	argvList: typing.List[typing.AnyStr] = []
	subArgStr: typing.AnyStr = sys.argv[index]
	while index < argLen:
		argvList = []
		# 选项
		argStr: typing.AnyStr = sys.argv[index]
		if argStr[0] == '-':
			argStr = argStr[1:]
			index += 1
			while index != argLen:
				subArgStr = sys.argv[index]
				if subArgStr[0] == '-':
					break
				argvList.append(subArgStr)
				index += 1
			if argvDict.get(argStr) is None:
				argvDict[argStr] = argvList
			else:
				for argItem in argvList:
					argvDict[argStr].append(argItem)
		else:
			while index != argLen:
				subArgStr = sys.argv[index]
				if subArgStr[0] == '-':
					break
				argvList.append(subArgStr)
				index += 1
			if argvDict.get("") is None:
				argvDict[""] = argvList
			else:
				for argItem in argvList:
					argvDict[""].append(argItem)
	return argvDict


def getPathPythonModels(pyFile: str) -> sys.modules:
	"""
	获取路径下的 py 脚本模块
	:param pyFile: py 文件
	:return: 返回一个模块，失败返回 None
	"""
	try:
		name = getBaseFileName(pyFile)
		path = getTargetAbsSavePath(pyFile)
		improtMode = importlib.util.find_spec(name, path)
		if improtMode is not None:
			return improtMode.loader.load_module()
	except:
		traceback.print_exc()
	return None


def readIni(iniFilePath: str, sectionName: str, key: str, defaultValue=None) -> str:
	"""
	从配置文件 ini 中获取一个值
	:param iniFilePath: 配置文件
	:type iniFilePath: str
	:param sectionName: 域
	:type sectionName: str
	:param key: 关键字
	:type key: str
	:param defaultValue: 默认值
	:type defaultValue: tuple
	:return: 成功返回获取值，失败返回 defaultValue
	:rtype: str
	"""
	global __multiProcessingGetRequestTimeLock, __multiProcessLock, __manager

	try:
		initLib()
		__multiProcessingGetRequestTimeLock.acquire()

		config = configparser.ConfigParser()
		readList = None
		try:
			## 无法打开，当即返回默认
			readList = config.read(iniFilePath)
		except:
			__stdError(traceback.format_exc())
			writeLogFile(traceback.format_exc(), httpUrl=sectionName)
			return defaultValue
		iniValue = None
		hasSelect = config.has_section(sectionName)
		try:
			if hasSelect:
				hasSelect = config.has_option(sectionName, key)
				if not hasSelect:
					config.set(sectionName, key, f"{defaultValue}")
					config.write(open(iniFilePath, "w"))
					return defaultValue
				iniValue = int(config.get(sectionName, key))
			else:
				config.add_section(sectionName)
				iniValue = defaultValue
				config.set(sectionName, key, f"{defaultValue}")
		except:
			__stdError(traceback.format_exc())
			writeLogFile(traceback.format_exc(), httpUrl=sectionName)
		if iniValue is None:
			config.add_section(sectionName)
			iniValue = defaultValue
			config.set(sectionName, key, f"{defaultValue}")
		config.write(open(iniFilePath, "w"))
	finally:
		__multiProcessingGetRequestTimeLock.release()
	return iniValue


__filterNovelNameList: list[str] = []
__initFilterFile = False


def filterateNovelName(novelName: str, readFilePath: list[str] = None) -> bool:
	"""
	过滤一个名称，存在过滤关键字则返回 True
	:param novelName: 小数名称
	:return: 存在过滤关键字则返回 True
	:rtype: bool
	"""
	global __filterNovelNameList, __initFilterFile, __multiProcessingGetRequestTimeLock
	try:

		if readFilePath is None or len(readFilePath) == 0:
			readFilePath = getFilterIniFilePath()
		initLib()
		__multiProcessingGetRequestTimeLock.acquire()
		novelName = novelName.upper()
		if not __initFilterFile:
			lock = getRWFileLock()
			try:
				lock.acquire()
				paths = getPathFilesNames(readFilePath)
				buffNameKey = []

				for path in paths:
					filterNovleNames = readFile(path).strip().replace("\r", "").replace(" ", "\n").replace("\t", "\n").split("\n")
					filterNovleNames = strListToChange(filterNovleNames, True)
					buffNameKey = strListreduce(filterNovleNames)
					for item in buffNameKey:
						__filterNovelNameList.append(item)
					__initFilterFile = True
					writeFile("\n".join(buffNameKey) + "\n", path)
			except:
				msg = traceback.format_exc()
				writeLogFile(msg, "read filter.ini error")
				__stdError(msg)
			finally:
				lock.release()
		for findItem in __filterNovelNameList:
			if novelName.rfind(findItem) != -1:
				return True
	finally:
		__multiProcessingGetRequestTimeLock.release()
	return False


def __sortStrNameFunKey(leftNameKeyStr: str, rightNameKeySyr: str) -> int:
	leftLen = len(leftNameKeyStr)
	rightLen = len(rightNameKeySyr)
	if leftLen < rightLen:
		return -1
	if leftLen > rightLen:
		return 1
	return leftNameKeyStr < rightNameKeySyr


def readIniFileToKeyList(filePath: str, updateFile: bool = True, sort=False, lock: threading.Lock = None) -> list[str]:
	"""
	读取一个配置文件，并且该配置文件以 '\\\\n' 为分隔符为过程，返回切分后的字符串数组
	它的元素不会出现重复
	:param filePath: 文件路径
	:param updateFile: 是否重新写入文件
	:param sort: 是否重新按照长度排序
	:param lock: 锁
	:return: 返回一个列表，失败返回 len(result) == 0，它不会返回 None
	"""
	resultList: list[str] = []
	try:
		if lock:
			lock.acquire()
		if filePath and os.path.exists(filePath):
			strList: list[str] = readFile(filePath).split("\n")
			## print(f"获取文件列表 {filePath}")
			for compStr in strList:
				compStr = strRemoveSpaces(compStr)
				if len(compStr) > 0:
					for buffStr in resultList:
						if buffStr == compStr:
							compStr = None
							break
				if compStr:
					resultList.append(compStr)
			nLen = len(resultList)
			if updateFile:
				# resultList = strListreduce(resultList)
				if nLen == 0:
					removeFile(filePath)
				elif sort:
					resultList.sort()
					resultList.sort(key=len)
					sort = False
				writeFile("\n".join(resultList) + "\n", filePath)
			if nLen == 0:
				writeFile("", filePath)
			elif sort:
				resultList.sort()
				resultList.sort(key=len)
			return resultList
		return resultList
	except:
		msg = f"readIniFileToKeyList error : {filePath}\n{getSysExcInfoErrorMsg()}"
		__stdError(msg)
		writeLogFile(msg, "readIniFileToKeyList Error")
	finally:
		if lock:
			lock.release()


def strListIsKey(key: str, forList: list[str]) -> bool:
	"""
	key 是否存在 forList 所有包含的字符串当中( == 查找)
	:param key: 查找的 key
	:param forList: 遍历的数组
	:return: 存在相等的key则返回True
	:rtype: bool
	"""
	for item in forList:
		if item == key:
			return True
	return False


def strListHasKey(key: str, forList: list[str]) -> bool:
	"""
	key 是否存在 forList 所有包含的子字符串当中(find 查找)
	存在返回 True，否则返回 False
	:param key: 查找的 key
	:param forList: 遍历的数组
	:return: 存在返回 True，否则返回 False
	:rtype: bool
	"""
	for item in forList:
		if key.find(item) != -1:
			return True
	return False


def strAtLenKeyToDict(strList: list[str]) -> dict[int, list[str]]:
	"""
	根据字符串数组中的字符长度给分配一个词典
	:param strList: 字符串数组
	:return: 分配的词典，它永远不会是 None
	"""
	result: dict[int, list[str]] = {}
	sizeLen = len(strList)
	if strList is None or sizeLen == 0:
		return result
	## 遍历
	item = ""
	for item in strList:
		item = item.strip()
		sizeLen = len(item)
		if sizeLen == 0:
			continue
		buffList = result.get(sizeLen)
		if buffList is None:
			result[sizeLen] = [item]
		else:
			result[sizeLen].append(item)
	return result


def strAtLenKeyDictReadFile(filePath: str) -> dict[int, list[str]]:
	"""
	从文件中获取列表，并且分类到 长度为 key， 字符串数组为 value 的词典对象上
	:param filePath: 文件路径
	:return: 词典列表
	"""
	result: dict[int, list[str]] = {}

	listBuff = readIniFileToKeyList(filePath, sort=True)
	if len(listBuff) == 0:
		return result
	## 去重
	return strAtLenKeyToDict(strListreduce(listBuff))


def strAtLenKeyDictsAppendStrList(left: list[str], rightDict: dict[int, list[str]]) -> dict[int, list[str]]:
	"""
	在词典当中追加一个字符串数组
	:param left: 追加列表
	:param rightDict: 被追加到的词典
	:return: 实现的词典
	"""
	result: dict[int, list[str]] = {}
	if rightDict is None:
		rightDict = result
	if left is None or len(left) == 0:
		return rightDict
	left = strListreduce(left)
	strLen = 0
	strItem = ""
	buffList = None
	for strItem in left:
		strItem = strItem.strip()
		strLen = len(strItem)
		if strLen == 0:
			continue
		buffList = rightDict.get(strLen)
		if buffList is None:
			rightDict[strLen] = [strItem]
		else:
			buffList.append(strItem)
	return rightDict


def strAtLenKeyMergeDicts(leftDict: dict[int, list[str]], rightDict: dict[int, list[str]]) -> dict[int, list[str]]:
	"""
	合并两个以长度为key，字符串数组为 value 的词典
	:param leftDict: 左侧词典
	:param rightDict: 右词典
	:return: 合并完成的词典
	"""
	result: dict[int, list[str]] = {}

	rightLen = len(rightDict)
	if leftDict is None:
		if rightDict is None:
			return result
		elif rightLen > 0:
			return rightDict
	leftLen = len(leftDict)
	if rightDict is None:
		if leftDict is None:
			return result
		elif leftLen > 0:
			return leftDict
	if rightLen == 0 and leftLen == 0:
		return result
	if rightLen == 0:
		return leftDict
	if leftLen == 0:
		return rightDict
	## 都存在数据
	items = leftDict.items()
	buffList: list[str] = None
	strItem = ""
	for key, value in items:
		buffList = rightDict[key]
		if buffList is None:
			rightDict[key] = value
		else:
			for strItem in value:
				if strListIsKey(strItem, buffList):
					continue
				buffList.append(strItem)
	return rightDict


def strDictHasStr(comStr: str, strDict: dict[int, list[str]]) -> bool:
	"""
	检查一个以字符串长度为 key 的字符列表词典是否存在指定字符串
	:param comStr: 检查的字符串
	:param strDict: 遍历的词典
	:return: 存在返回 True，否则返回 False
	"""
	strLen = len(comStr)
	checkList = strDict.get(strLen)
	if checkList is None:
		return False
	strLen = len(checkList)
	if strLen == 0:
		return False
	if strListIsKey(comStr, checkList):
		return True
	return False


def strDictHasStrSubStr(comStr: str, strDict: dict[int, list[str]]) -> bool:
	"""
	检查一个以字符串长度为 key 的字符列表词典是否存在指定子字符串
	:param comStr: 检查的字符串
	:param strDict: 遍历的词典
	:return: 存在返回 True，否则返回 False
	"""
	strLen = len(comStr)
	findStr = ""
	items = strDict.items()
	for key, value in items:
		if key <= strLen and strListHasKey(comStr, value):
			return True
	return False


if __name__ == '__main__':
	print(getSysExcInfoErrorMsg())
